summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2019-02-11 20:39:36 +0100
committerChristian Grothoff <christian@grothoff.org>2019-02-11 20:39:36 +0100
commit1f59e703d82b47f3aeaf432045a2633c2841169b (patch)
tree6af5609b388cf1906a29b5d572bec2dd8fb2ae1c /src
initial import from gnunet.git
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am7
-rw-r--r--src/include/.gitignore2
-rw-r--r--src/include/Makefile.am15
-rw-r--r--src/include/gnunet_multicast_service.h925
-rw-r--r--src/include/gnunet_psyc_env.h340
-rw-r--r--src/include/gnunet_psyc_message.h278
-rw-r--r--src/include/gnunet_psyc_service.h1364
-rw-r--r--src/include/gnunet_psyc_slicer.h378
-rw-r--r--src/include/gnunet_psyc_util_lib.h53
-rw-r--r--src/include/gnunet_psycstore_plugin.h383
-rw-r--r--src/include/gnunet_psycstore_service.h701
-rw-r--r--src/include/gnunet_social_service.h1344
-rw-r--r--src/multicast/.gitignore7
-rw-r--r--src/multicast/Makefile.am79
-rw-r--r--src/multicast/gnunet-multicast.c79
-rw-r--r--src/multicast/gnunet-service-multicast.c2234
-rw-r--r--src/multicast/multicast.conf.in22
-rw-r--r--src/multicast/multicast.h303
-rw-r--r--src/multicast/multicast_api.c1399
-rw-r--r--src/multicast/test_multicast.c758
-rw-r--r--src/multicast/test_multicast.conf56
-rw-r--r--src/multicast/test_multicast_2peers.c520
-rw-r--r--src/multicast/test_multicast_line.conf63
-rw-r--r--src/multicast/test_multicast_multipeer.c643
-rw-r--r--src/multicast/test_multicast_star.conf64
-rw-r--r--src/psyc/.gitignore2
-rw-r--r--src/psyc/Makefile.am77
-rw-r--r--src/psyc/gnunet-service-psyc.c2860
-rw-r--r--src/psyc/psyc.conf.in12
-rw-r--r--src/psyc/psyc.h178
-rw-r--r--src/psyc/psyc_api.c1584
-rw-r--r--src/psyc/psyc_test_lib.h67
-rw-r--r--src/psyc/test_psyc.c1018
-rw-r--r--src/psyc/test_psyc2.c284
-rw-r--r--src/psyc/test_psyc_api_join.c282
-rw-r--r--src/psycstore/.gitignore5
-rw-r--r--src/psycstore/Makefile.am155
-rw-r--r--src/psycstore/gnunet-service-psycstore.c1049
-rw-r--r--src/psycstore/plugin_psycstore_mysql.c1960
-rw-r--r--src/psycstore/plugin_psycstore_postgres.c1530
-rw-r--r--src/psycstore/plugin_psycstore_sqlite.c1948
-rw-r--r--src/psycstore/psycstore.conf.in28
-rw-r--r--src/psycstore/psycstore.h520
-rw-r--r--src/psycstore/psycstore_api.c1285
-rw-r--r--src/psycstore/test_plugin_psycstore.c532
-rw-r--r--src/psycstore/test_plugin_psycstore_mysql.conf7
-rw-r--r--src/psycstore/test_plugin_psycstore_postgres.conf2
-rw-r--r--src/psycstore/test_plugin_psycstore_sqlite.conf2
-rw-r--r--src/psycstore/test_psycstore.c586
-rw-r--r--src/psycstore/test_psycstore.conf8
-rw-r--r--src/psycutil/.gitignore1
-rw-r--r--src/psycutil/Makefile.am45
-rw-r--r--src/psycutil/psyc_env.c196
-rw-r--r--src/psycutil/psyc_message.c1355
-rw-r--r--src/psycutil/psyc_slicer.c711
-rw-r--r--src/psycutil/test_psyc_env.c96
-rw-r--r--src/social/.gitignore3
-rw-r--r--src/social/Makefile.am79
-rw-r--r--src/social/gnunet-service-social.c3760
-rw-r--r--src/social/gnunet-social.c1411
-rw-r--r--src/social/social.conf.in15
-rw-r--r--src/social/social.h292
-rw-r--r--src/social/social_api.c2827
-rw-r--r--src/social/test_social.c1449
-rw-r--r--src/social/test_social.conf19
66 files changed, 40259 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..282522d
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..42185e6
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,7 @@
+# This Makefile.am is in the public domain
+SUBDIRS = include \
+ multicast \
+ psycutil \
+ psycstore \
+ psyc \
+ social
diff --git a/src/include/.gitignore b/src/include/.gitignore
new file mode 100644
index 0000000..282522d
--- /dev/null
+++ b/src/include/.gitignore
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
new file mode 100644
index 0000000..8b8bcba
--- /dev/null
+++ b/src/include/Makefile.am
@@ -0,0 +1,15 @@
+# This Makefile.am is in the public domain
+SUBDIRS = .
+
+gnunetincludedir = $(includedir)/gnunet
+
+gnunetinclude_HEADERS = \
+ gnunet_multicast_service.h \
+ gnunet_psycstore_plugin.h \
+ gnunet_psycstore_service.h \
+ gnunet_psyc_service.h \
+ gnunet_psyc_util_lib.h \
+ gnunet_psyc_env.h \
+ gnunet_psyc_message.h \
+ gnunet_psyc_slicer.h \
+ gnunet_social_service.h
diff --git a/src/include/gnunet_multicast_service.h b/src/include/gnunet_multicast_service.h
new file mode 100644
index 0000000..58fca0b
--- /dev/null
+++ b/src/include/gnunet_multicast_service.h
@@ -0,0 +1,925 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013 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 Gabor X Toth
+ * @author Christian Grothoff
+ *
+ * @file
+ * Multicast service; multicast messaging via CADET
+ *
+ * @defgroup multicast Multicast service
+ * Multicast messaging via CADET.
+ * @{
+ */
+
+#ifndef GNUNET_MULTICAST_SERVICE_H
+#define GNUNET_MULTICAST_SERVICE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+#include "gnunet_util_lib.h"
+#include "gnunet_transport_service.h"
+
+/**
+ * Version number of GNUnet-multicast API.
+ */
+#define GNUNET_MULTICAST_VERSION 0x00000000
+
+/**
+ * Opaque handle for a multicast group member.
+ */
+struct GNUNET_MULTICAST_Member;
+
+/**
+ * Handle for the origin of a multicast group.
+ */
+struct GNUNET_MULTICAST_Origin;
+
+
+enum GNUNET_MULTICAST_MessageFlags
+{
+ /**
+ * First fragment of a message.
+ */
+ GNUNET_MULTICAST_MESSAGE_FIRST_FRAGMENT = 1 << 0,
+
+ /**
+ * Last fragment of a message.
+ */
+ GNUNET_MULTICAST_MESSAGE_LAST_FRAGMENT = 1 << 1,
+
+ /**
+ * OR'ed flags if message is not fragmented.
+ */
+ GNUNET_MULTICAST_MESSAGE_NOT_FRAGMENTED
+ = GNUNET_MULTICAST_MESSAGE_FIRST_FRAGMENT
+ | GNUNET_MULTICAST_MESSAGE_LAST_FRAGMENT,
+
+ /**
+ * Historic message, used only locally when replaying messages from local
+ * storage.
+ */
+ GNUNET_MULTICAST_MESSAGE_HISTORIC = 1 << 30
+
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Header of a multicast message fragment.
+ *
+ * This format is public as the replay mechanism must replay message fragments using the
+ * same format. This is needed as we want to integrity-check message fragments within
+ * the multicast layer to avoid multicasting mal-formed messages.
+ */
+struct GNUNET_MULTICAST_MessageHeader
+{
+
+ /**
+ * Header for all multicast message fragments from the origin.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Number of hops this message fragment has taken since the origin.
+ *
+ * Helpful to determine shortest paths to the origin among honest peers for
+ * unicast requests from members. Updated at each hop and thus not signed and
+ * not secure.
+ */
+ uint32_t hop_counter GNUNET_PACKED;
+
+ /**
+ * ECC signature of the message fragment.
+ *
+ * Signature must match the public key of the multicast group.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature signature;
+
+ /**
+ * Purpose for the signature and size of the signed data.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Number of the message fragment, monotonically increasing starting from 1.
+ */
+ uint64_t fragment_id GNUNET_PACKED;
+
+ /**
+ * Byte offset of this @e fragment of the @e message.
+ */
+ uint64_t fragment_offset GNUNET_PACKED;
+
+ /**
+ * Number of the message this fragment belongs to.
+ *
+ * Set in GNUNET_MULTICAST_origin_to_all().
+ */
+ uint64_t message_id GNUNET_PACKED;
+
+ /**
+ * Counter that monotonically increases whenever a member parts the group.
+ *
+ * Set in GNUNET_MULTICAST_origin_to_all().
+ *
+ * It has significance in case of replay requests: when a member has missed
+ * messages and gets a replay request: in this case if the @a group_generation
+ * is still the same before and after the missed messages, it means that no
+ * @e join or @e part operations happened during the missed messages.
+ */
+ uint64_t group_generation GNUNET_PACKED;
+
+ /**
+ * Flags for this message fragment.
+ *
+ * @see enum GNUNET_MULTICAST_MessageFlags
+ */
+ uint32_t flags GNUNET_PACKED;
+
+ /* Followed by message body. */
+};
+
+
+/**
+ * Header of a request from a member to the origin.
+ */
+struct GNUNET_MULTICAST_RequestHeader
+{
+ /**
+ * Header for all requests from a member to the origin.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Public key of the sending member.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+ /**
+ * ECC signature of the request fragment.
+ *
+ * Signature must match the public key of the multicast group.
+ */
+ struct GNUNET_CRYPTO_EcdsaSignature signature;
+
+ /**
+ * Purpose for the signature and size of the signed data.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Number of the request fragment.
+ * Monotonically increasing from 1.
+ */
+ uint64_t fragment_id GNUNET_PACKED;
+
+ /**
+ * Byte offset of this @e fragment of the @e request.
+ */
+ uint64_t fragment_offset GNUNET_PACKED;
+
+ /**
+ * Number of the request this fragment belongs to.
+ *
+ * Set in GNUNET_MULTICAST_origin_to_all().
+ */
+ uint64_t request_id GNUNET_PACKED;
+
+ /**
+ * Flags for this request.
+ */
+ enum GNUNET_MULTICAST_MessageFlags flags GNUNET_PACKED;
+
+ /* Followed by request body. */
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Maximum size of a multicast message fragment.
+ */
+#define GNUNET_MULTICAST_FRAGMENT_MAX_SIZE (63 * 1024)
+
+#define GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \
+ (GNUNET_MULTICAST_FRAGMENT_MAX_SIZE \
+ - sizeof (struct GNUNET_MULTICAST_MessageHeader))
+
+
+/**
+ * Handle that identifies a join request.
+ *
+ * Used to match calls to #GNUNET_MULTICAST_JoinRequestCallback to the
+ * corresponding calls to #GNUNET_MULTICAST_join_decision().
+ */
+struct GNUNET_MULTICAST_JoinHandle;
+
+
+/**
+ * Function to call with the decision made for a join request.
+ *
+ * Must be called once and only once in response to an invocation of the
+ * #GNUNET_MULTICAST_JoinRequestCallback.
+ *
+ * @param jh
+ * Join request handle.
+ * @param is_admitted
+ * #GNUNET_YES if the join is approved,
+ * #GNUNET_NO if it is disapproved,
+ * #GNUNET_SYSERR if we cannot answer the request.
+ * @param relay_count
+ * Number of relays given.
+ * @param relays
+ * Array of suggested peers that might be useful relays to use
+ * when joining the multicast group (essentially a list of peers that
+ * are already part of the multicast group and might thus be willing
+ * to help with routing). If empty, only this local peer (which must
+ * be the multicast origin) is a good candidate for building the
+ * multicast tree. Note that it is unnecessary to specify our own
+ * peer identity in this array.
+ * @param join_resp
+ * Message to send in response to the joining peer;
+ * can also be used to redirect the peer to a different group at the
+ * application layer; this response is to be transmitted to the
+ * peer that issued the request even if admission is denied.
+ */
+struct GNUNET_MULTICAST_ReplayHandle *
+GNUNET_MULTICAST_join_decision (struct GNUNET_MULTICAST_JoinHandle *jh,
+ int is_admitted,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_resp);
+
+
+/**
+ * Method called whenever another peer wants to join the multicast group.
+ *
+ * Implementations of this function must call GNUNET_MULTICAST_join_decision()
+ * with the decision.
+ *
+ * @param cls
+ * Closure.
+ * @param member_pub_key
+ * Public key of the member requesting join.
+ * @param join_msg
+ * Application-dependent join message from the new member.
+ * @param jh
+ * Join handle to pass to GNUNET_MULTICAST_join_decison().
+ */
+typedef void
+(*GNUNET_MULTICAST_JoinRequestCallback) (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh);
+
+
+/**
+ * Method called to inform about the decision in response to a join request.
+ *
+ * If @a is_admitted is not #GNUNET_YES, then the multicast service disconnects
+ * the client and the multicast member handle returned by
+ * GNUNET_MULTICAST_member_join() is invalidated.
+ *
+ * @param cls
+ * Closure.
+ * @param is_admitted
+ * #GNUNET_YES or #GNUNET_NO or #GNUNET_SYSERR
+ * @param peer
+ * The peer we are connected to and the join decision is from.
+ * @param relay_count
+ * Number of peers in the @a relays array.
+ * @param relays
+ * Peer identities of members of the group, which serve as relays
+ * and can be used to join the group at. If empty, only the origin can
+ * be used to connect to the group.
+ * @param join_msg
+ * Application-dependent join message from the origin.
+ */
+typedef void
+(*GNUNET_MULTICAST_JoinDecisionCallback) (void *cls,
+ int is_admitted,
+ const struct GNUNET_PeerIdentity *peer,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_msg);
+
+
+/**
+ * Function called whenever a group member has transmitted a request
+ * to the origin (other than joining or leaving).
+ *
+ * FIXME: need to distinguish between origin cancelling a message (some fragments
+ * were sent, then the rest 'discarded') and the case where we got disconnected;
+ * right now, both would mean 'msg' is NULL, but they could be quite different...
+ * So the semantics from the receiver side of
+ * GNUNET_MULTICAST_member_to_origin_cancel() are not clear here. Maybe we
+ * should do something with the flags in this case?
+ *
+ * @param cls
+ * Closure (set from GNUNET_MULTICAST_origin_start).
+ * @param sender
+ * Identity of the sender.
+ * @param req
+ * Request to the origin.
+ * @param flags
+ * Flags for the request.
+ */
+typedef void
+(*GNUNET_MULTICAST_RequestCallback) (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req);
+
+
+/**
+ * Function called whenever a group member is receiving a message fragment from
+ * the origin.
+ *
+ * If admission to the group is denied, this function is called once with the
+ * response of the @e origin (as given to GNUNET_MULTICAST_join_decision()) and
+ * then a second time with NULL to indicate that the connection failed for good.
+ *
+ * FIXME: need to distinguish between origin cancelling a message (some fragments
+ * were sent, then the rest 'discarded') and the case where we got disconnected;
+ * right now, both would mean 'msg' is NULL, but they could be quite different...
+ * So the semantics from the receiver side of
+ * GNUNET_MULTICAST_origin_to_all_cancel() are not clear here.
+ *
+ * @param cls
+ * Closure (set from GNUNET_MULTICAST_member_join())
+ * @param msg
+ * Message from the origin, NULL if the origin shut down
+ * (or we were kicked out, and we should thus call
+ * GNUNET_MULTICAST_member_part() next)
+ */
+typedef void
+(*GNUNET_MULTICAST_MessageCallback) (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg);
+
+
+/**
+ * Opaque handle to a replay request from the multicast service.
+ */
+struct GNUNET_MULTICAST_ReplayHandle;
+
+
+/**
+ * Functions with this signature are called whenever the multicast service needs
+ * a message fragment to be replayed by fragment_id.
+ *
+ * Implementations of this function MUST call GNUNET_MULTICAST_replay() ONCE
+ * (with a message or an error); however, if the origin is destroyed or the
+ * group is left, the replay handle must no longer be used.
+ *
+ * @param cls
+ * Closure (set from GNUNET_MULTICAST_origin_start()
+ * or GNUNET_MULTICAST_member_join()).
+ * @param member_pub_key
+ * The member requesting replay.
+ * @param fragment_id
+ * Which message fragment should be replayed.
+ * @param flags
+ * Flags for the replay.
+ * @param rh
+ * Handle to pass to message transmit function.
+ */
+typedef void
+(*GNUNET_MULTICAST_ReplayFragmentCallback) (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ uint64_t fragment_id,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh);
+
+/**
+ * Functions with this signature are called whenever the multicast service needs
+ * a message fragment to be replayed by message_id and fragment_offset.
+ *
+ * Implementations of this function MUST call GNUNET_MULTICAST_replay() ONCE
+ * (with a message or an error); however, if the origin is destroyed or the
+ * group is left, the replay handle must no longer be used.
+ *
+ * @param cls
+ * Closure (set from GNUNET_MULTICAST_origin_start()
+ * or GNUNET_MULTICAST_member_join()).
+ * @param member_pub_key
+ * The member requesting replay.
+ * @param message_id
+ * Which message should be replayed.
+ * @param fragment_offset
+ * Offset of the fragment within of @a message_id to be replayed.
+ * @param flags
+ * Flags for the replay.
+ * @param rh
+ * Handle to pass to message transmit function.
+ */
+typedef void
+(*GNUNET_MULTICAST_ReplayMessageCallback) (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh);
+
+
+/**
+ * Possible error codes during replay.
+ */
+enum GNUNET_MULTICAST_ReplayErrorCode
+{
+
+ /**
+ * Everything is fine.
+ */
+ GNUNET_MULTICAST_REC_OK = 0,
+
+ /**
+ * Message fragment not found in the message store.
+ *
+ * Either discarded if it is too old, or not arrived yet if this member has
+ * missed some messages.
+ */
+ GNUNET_MULTICAST_REC_NOT_FOUND = 1,
+
+ /**
+ * Fragment ID counter was larger than the highest counter this
+ * replay function has ever encountered; thus it is likely the
+ * origin never sent it and we're at the HEAD of the multicast
+ * stream as far as this node is concerned.
+ *
+ * FIXME: needed?
+ */
+ GNUNET_MULTICAST_REC_PAST_HEAD = 2,
+
+ /**
+ * Access is denied to the requested fragment, membership test did not pass.
+ */
+ GNUNET_MULTICAST_REC_ACCESS_DENIED = 3,
+
+ /**
+ * Internal error (i.e. database error). Try some other peer.
+ */
+ GNUNET_MULTICAST_REC_INTERNAL_ERROR = 4
+
+};
+
+
+/**
+ * Replay a message fragment for the multicast group.
+ *
+ * @param rh
+ * Replay handle identifying which replay operation was requested.
+ * @param msg
+ * Replayed message fragment, NULL if not found / an error occurred.
+ * @param ec
+ * Error code. See enum GNUNET_MULTICAST_ReplayErrorCode
+ * If not #GNUNET_MULTICAST_REC_OK, the replay handle is invalidated.
+ */
+void
+GNUNET_MULTICAST_replay_response (struct GNUNET_MULTICAST_ReplayHandle *rh,
+ const struct GNUNET_MessageHeader *msg,
+ enum GNUNET_MULTICAST_ReplayErrorCode ec);
+
+
+/**
+ * Indicate the end of the replay session.
+ *
+ * Invalidates the replay handle.
+ *
+ * @param rh Replay session to end.
+ */
+void
+GNUNET_MULTICAST_replay_response_end (struct GNUNET_MULTICAST_ReplayHandle *rh);
+
+
+/**
+ * Function called to provide data for a transmission for a replay.
+ *
+ * @see GNUNET_MULTICAST_replay2()
+ */
+typedef int
+(*GNUNET_MULTICAST_ReplayTransmitNotify) (void *cls,
+ size_t *data_size,
+ void *data);
+
+
+/**
+ * Replay a message for the multicast group.
+ *
+ * @param rh
+ * Replay handle identifying which replay operation was requested.
+ * @param notify
+ * Function to call to get the message.
+ * @param notify_cls
+ * Closure for @a notify.
+ */
+void
+GNUNET_MULTICAST_replay_response2 (struct GNUNET_MULTICAST_ReplayHandle *rh,
+ GNUNET_MULTICAST_ReplayTransmitNotify notify,
+ void *notify_cls);
+
+
+/**
+ * Start a multicast group.
+ *
+ * Peers that issue GNUNET_MULTICAST_member_join() can transmit a join request
+ * to either an existing group member or to the origin. If the joining is
+ * approved, the member is cleared for @e replay and will begin to receive
+ * messages transmitted to the group. If joining is disapproved, the failed
+ * candidate will be given a response. Members in the group can send messages
+ * to the origin.
+ *
+ * TODO: This function could optionally offer to advertise the origin in the
+ * P2P overlay network(where?) under the respective public key so that other
+ * peers can find an alternate PeerId to join it. Higher level protocols may
+ * however provide other means of solving the problem of the offline host
+ * (see secushare specs about that) and therefore merely need a way to provide
+ * a list of possible PeerIds.
+ *
+ * @param cfg
+ * Configuration to use.
+ * @param priv_key
+ * ECC key that will be used to sign messages for this
+ * multicast session; public key is used to identify the multicast group;
+ * @param max_fragment_id
+ * Maximum fragment ID already sent to the group.
+ * 0 for a new group.
+ * @param join_request_cb
+ * Function called to approve / disapprove joining of a peer.
+ * @param replay_frag_cb
+ * Function that can be called to replay a message fragment.
+ * @param replay_msg_cb
+ * Function that can be called to replay a message.
+ * @param request_cb
+ * Function called with message fragments from group members.
+ * @param message_cb
+ * Function called with the message fragments sent to the
+ * network by GNUNET_MULTICAST_origin_to_all(). These message fragments
+ * should be stored for answering replay requests later.
+ * @param cls
+ * Closure for the various callbacks that follow.
+ *
+ * @return Handle for the origin, NULL on error.
+ */
+struct GNUNET_MULTICAST_Origin *
+GNUNET_MULTICAST_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *priv_key,
+ uint64_t max_fragment_id,
+ GNUNET_MULTICAST_JoinRequestCallback join_request_cb,
+ GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb,
+ GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb,
+ GNUNET_MULTICAST_RequestCallback request_cb,
+ GNUNET_MULTICAST_MessageCallback message_cb,
+ void *cls);
+
+/**
+ * Function called to provide data for a transmission from the origin to all
+ * members.
+ *
+ * Note that returning #GNUNET_OK or #GNUNET_SYSERR (but not #GNUNET_NO)
+ * invalidates the respective transmission handle.
+ *
+ * @param cls
+ * Closure.
+ * @param[in,out] data_size
+ * Initially set to the number of bytes available in
+ * @a data, should be set to the number of bytes written to data.
+ * @param[out] data
+ * Where to write the body of the message to give to the
+ * method. The function must copy at most @a data_size bytes to @a data.
+ *
+ * @return #GNUNET_SYSERR on error (fatal, aborts transmission)
+ * #GNUNET_NO on success, if more data is to be transmitted later.
+ * Should be used if @a data_size was not big enough to take all the
+ * data. If 0 is returned in @a data_size the transmission is paused,
+ * and can be resumed with GNUNET_MULTICAST_origin_to_all_resume().
+ * #GNUNET_YES if this completes the transmission (all data supplied)
+ * @deprecated should move to MQ-style API!
+ */
+typedef int
+(*GNUNET_MULTICAST_OriginTransmitNotify) (void *cls,
+ size_t *data_size,
+ void *data);
+
+
+/**
+ * Handle for a request to send a message to all multicast group members
+ * (from the origin).
+ */
+struct GNUNET_MULTICAST_OriginTransmitHandle;
+
+
+/**
+ * Send a message to the multicast group.
+ *
+ * @param origin
+ * Handle to the multicast group.
+ * @param message_id
+ * Application layer ID for the message. Opaque to multicast.
+ * @param group_generation
+ * Group generation of the message. Documented in
+ * struct GNUNET_MULTICAST_MessageHeader.
+ * @param notify
+ * Function to call to get the message.
+ * @param notify_cls
+ * Closure for @a notify.
+ *
+ * @return NULL on error (i.e. request already pending).
+ * @deprecated should move to MQ-style API!
+ */
+struct GNUNET_MULTICAST_OriginTransmitHandle *
+GNUNET_MULTICAST_origin_to_all (struct GNUNET_MULTICAST_Origin *origin,
+ uint64_t message_id,
+ uint64_t group_generation,
+ GNUNET_MULTICAST_OriginTransmitNotify notify,
+ void *notify_cls);
+
+
+
+/**
+ * Resume message transmission to multicast group.
+ *
+ * @param th Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_origin_to_all_resume (struct GNUNET_MULTICAST_OriginTransmitHandle *th);
+
+
+/**
+ * Cancel request for message transmission to multicast group.
+ *
+ * @param th Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_origin_to_all_cancel (struct GNUNET_MULTICAST_OriginTransmitHandle *th);
+
+
+/**
+ * Stop a multicast group.
+ *
+ * @param origin Multicast group to stop.
+ */
+void
+GNUNET_MULTICAST_origin_stop (struct GNUNET_MULTICAST_Origin *origin,
+ GNUNET_ContinuationCallback stop_cb,
+ void *stop_cls);
+
+
+/**
+ * Join a multicast group.
+ *
+ * The entity joining is always the local peer. Further information about the
+ * candidate can be provided in @a join_msg. If the join fails, the
+ * @a message_cb is invoked with a (failure) response and then with NULL. If
+ * the join succeeds, outstanding (state) messages and ongoing multicast
+ * messages will be given to the @a message_cb until the member decides to part
+ * the group. The @a mem_test_cb and @a replay_cb functions may be called at
+ * anytime by the multicast service to support relaying messages to other
+ * members of the group.
+ *
+ * @param cfg
+ * Configuration to use.
+ * @param group_key
+ * ECC public key that identifies the group to join.
+ * @param member_pub_key
+ * ECC key that identifies the member
+ * and used to sign requests sent to the origin.
+ * @param origin
+ * Peer ID of the origin to send unicast requsets to. If NULL,
+ * unicast requests are sent back via multiple hops on the reverse path
+ * of multicast messages.
+ * @param relay_count
+ * Number of peers in the @a relays array.
+ * @param relays
+ * Peer identities of members of the group, which serve as relays
+ * and can be used to join the group at. and send the @a join_request to.
+ * If empty, the @a join_request is sent directly to the @a origin.
+ * @param join_msg
+ * Application-dependent join message to be passed to the peer @a origin.
+ * @param join_request_cb
+ * Function called to approve / disapprove joining of a peer.
+ * @param join_decision_cb
+ * Function called to inform about the join decision.
+ * @param replay_frag_cb
+ * Function that can be called to replay message fragments
+ * this peer already knows from this group. NULL if this
+ * client is unable to support replay.
+ * @param replay_msg_cb
+ * Function that can be called to replay message fragments
+ * this peer already knows from this group. NULL if this
+ * client is unable to support replay.
+ * @param message_cb
+ * Function to be called for all message fragments we
+ * receive from the group, excluding those our @a replay_cb
+ * already has.
+ * @param cls
+ * Closure for callbacks.
+ *
+ * @return Handle for the member, NULL on error.
+ */
+struct GNUNET_MULTICAST_Member *
+GNUNET_MULTICAST_member_join (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *group_key,
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *member_pub_key,
+ const struct GNUNET_PeerIdentity *origin,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_request,
+ GNUNET_MULTICAST_JoinRequestCallback join_request_cb,
+ GNUNET_MULTICAST_JoinDecisionCallback join_decision_cb,
+ GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb,
+ GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb,
+ GNUNET_MULTICAST_MessageCallback message_cb,
+ void *cls);
+
+/**
+ * Handle for a replay request.
+ */
+struct GNUNET_MULTICAST_MemberReplayHandle;
+
+
+/**
+ * Request a fragment to be replayed by fragment ID.
+ *
+ * Useful if messages below the @e max_known_fragment_id given when joining are
+ * needed and not known to the client.
+ *
+ * @param member
+ * Membership handle.
+ * @param fragment_id
+ * ID of a message fragment that this client would like to see replayed.
+ * @param flags
+ * Additional flags for the replay request.
+ * It is used and defined by GNUNET_MULTICAST_ReplayFragmentCallback
+ *
+ * @return Replay request handle, NULL on error.
+ */
+struct GNUNET_MULTICAST_MemberReplayHandle *
+GNUNET_MULTICAST_member_replay_fragment (struct GNUNET_MULTICAST_Member *member,
+ uint64_t fragment_id,
+ uint64_t flags);
+
+
+/**
+ * Request a message fr to be replayed.
+ *
+ * Useful if messages below the @e max_known_fragment_id given when joining are
+ * needed and not known to the client.
+ *
+ * @param member
+ * Membership handle.
+ * @param message_id
+ * ID of the message this client would like to see replayed.
+ * @param fragment_offset
+ * Offset of the fragment within the message to replay.
+ * @param flags
+ * Additional flags for the replay request.
+ * It is used & defined by GNUNET_MULTICAST_ReplayMessageCallback
+ *
+ * @return Replay request handle, NULL on error.
+ */
+struct GNUNET_MULTICAST_MemberReplayHandle *
+GNUNET_MULTICAST_member_replay_message (struct GNUNET_MULTICAST_Member *member,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags);
+
+
+/**
+ * Cancel a replay request.
+ *
+ * @param rh
+ * Request to cancel.
+ */
+void
+GNUNET_MULTICAST_member_replay_cancel (struct GNUNET_MULTICAST_MemberReplayHandle *rh);
+
+
+/**
+ * Part a multicast group.
+ *
+ * Disconnects from all group members and invalidates the @a member handle.
+ *
+ * An application-dependent part message can be transmitted beforehand using
+ * #GNUNET_MULTICAST_member_to_origin())
+ *
+ * @param member
+ * Membership handle.
+ */
+void
+GNUNET_MULTICAST_member_part (struct GNUNET_MULTICAST_Member *member,
+ GNUNET_ContinuationCallback part_cb,
+ void *part_cls);
+
+
+/**
+ * Function called to provide data for a transmission from a member to the origin.
+ *
+ * Note that returning #GNUNET_OK or #GNUNET_SYSERR (but not #GNUNET_NO)
+ * invalidates the respective transmission handle.
+ *
+ * @param cls
+ * Closure.
+ * @param[in,out] data_size
+ * Initially set to the number of bytes available in
+ * @a data, should be set to the number of bytes written to data.
+ * @param[out] data
+ * Where to write the body of the message to give to the
+ * method. The function must copy at most @a data_size bytes to @a data.
+ *
+ * @return #GNUNET_SYSERR on error (fatal, aborts transmission)
+ * #GNUNET_NO on success, if more data is to be transmitted later.
+ * Should be used if @a data_size was not big enough to take all the
+ * data. If 0 is returned in @a data_size the transmission is paused,
+ * and can be resumed with GNUNET_MULTICAST_member_to_origin_resume().
+ * #GNUNET_YES if this completes the transmission (all data supplied)
+ * @deprecated should move to MQ-style API!
+ */
+typedef int
+(*GNUNET_MULTICAST_MemberTransmitNotify) (void *cls,
+ size_t *data_size,
+ void *data);
+
+
+/**
+ * Handle for a message to be delivered from a member to the origin.
+ */
+struct GNUNET_MULTICAST_MemberTransmitHandle;
+
+
+/**
+ * Send a message to the origin of the multicast group.
+ *
+ * @param member
+ * Membership handle.
+ * @param request_id
+ * Application layer ID for the request. Opaque to multicast.
+ * @param notify
+ * Callback to call to get the message.
+ * @param notify_cls
+ * Closure for @a notify.
+ *
+ * @return Handle to cancel request, NULL on error (i.e. request already pending).
+ * @deprecated should move to MQ-style API!
+ */
+struct GNUNET_MULTICAST_MemberTransmitHandle *
+GNUNET_MULTICAST_member_to_origin (struct GNUNET_MULTICAST_Member *member,
+ uint64_t request_id,
+ GNUNET_MULTICAST_MemberTransmitNotify notify,
+ void *notify_cls);
+
+
+/**
+ * Resume message transmission to origin.
+ *
+ * @param th
+ * Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_member_to_origin_resume (struct GNUNET_MULTICAST_MemberTransmitHandle *th);
+
+
+/**
+ * Cancel request for message transmission to origin.
+ *
+ * @param th
+ * Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_member_to_origin_cancel (struct GNUNET_MULTICAST_MemberTransmitHandle *th);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_MULTICAST_SERVICE_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_psyc_env.h b/src/include/gnunet_psyc_env.h
new file mode 100644
index 0000000..0d878cb
--- /dev/null
+++ b/src/include/gnunet_psyc_env.h
@@ -0,0 +1,340 @@
+/*
+ * This file is part of GNUnet.
+ * Copyright (C) 2013 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 Gabor X Toth
+ *
+ * @file
+ * PSYC Environment library
+ *
+ * @defgroup psyc-util-env PSYC Utilities library: Environment
+ * Environment data structure operations for PSYC and Social messages.
+ *
+ * Library providing operations for the @e environment of
+ * PSYC and Social messages, and for (de)serializing variable values.
+ *
+ * @{
+ */
+
+
+#ifndef GNUNET_PSYC_ENV_H
+#define GNUNET_PSYC_ENV_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+
+/**
+ * Possible operations on PSYC state (persistent) and transient variables (per message).
+ */
+enum GNUNET_PSYC_Operator
+{
+ /**
+ * Set value of a transient variable.
+ */
+ GNUNET_PSYC_OP_SET = ':',
+
+ /**
+ * Assign value for a persistent state variable.
+ *
+ * If an assigned value is NULL, the variable is deleted.
+ */
+ GNUNET_PSYC_OP_ASSIGN = '=',
+
+ /**
+ * Augment state variable.
+ *
+ * Used for appending strings, adding numbers, and adding new items to a list or dictionary.
+ */
+ GNUNET_PSYC_OP_AUGMENT = '+',
+
+ /**
+ * Diminish state variable.
+ *
+ * Used for subtracting numbers, and removing items from a list or dictionary.
+ */
+ GNUNET_PSYC_OP_DIMINISH = '-',
+
+ /**
+ * Update state variable.
+ *
+ * Used for modifying a single item of a list or dictionary.
+ */
+ GNUNET_PSYC_OP_UPDATE = '@',
+};
+
+
+/**
+ * PSYC variable types.
+ */
+enum GNUNET_PSYC_Type
+{
+ GNUNET_PSYC_TYPE_DATA = 0,
+ GNUNET_PSYC_TYPE_NUMBER,
+ GNUNET_PSYC_TYPE_LIST,
+ GNUNET_PSYC_TYPE_DICT
+};
+
+
+/**
+ * PSYC state modifier.
+ */
+struct GNUNET_PSYC_Modifier
+{
+ /**
+ * State operation.
+ */
+ enum GNUNET_PSYC_Operator oper;
+
+ /**
+ * Variable name.
+ */
+ const char *name;
+
+ /**
+ * Size of @a value.
+ */
+ size_t value_size;
+
+ /**
+ * Value of variable.
+ */
+ const void *value;
+
+ /**
+ * Next modifier.
+ */
+ struct GNUNET_PSYC_Modifier *next;
+
+ /**
+ * Previous modifier.
+ */
+ struct GNUNET_PSYC_Modifier *prev;
+};
+
+
+/**
+ * Environment for a message.
+ *
+ * Contains modifiers.
+ */
+struct GNUNET_PSYC_Environment;
+
+
+/**
+ * Create an environment.
+ *
+ * @return A newly allocated environment.
+ */
+struct GNUNET_PSYC_Environment *
+GNUNET_PSYC_env_create ();
+
+
+/**
+ * Add a modifier to the environment.
+ *
+ * @param env The environment.
+ * @param oper Operation to perform.
+ * @param name Name of the variable.
+ * @param value Value of the variable.
+ * @param value_size Size of @a value.
+ */
+void
+GNUNET_PSYC_env_add (struct GNUNET_PSYC_Environment *env,
+ enum GNUNET_PSYC_Operator oper, const char *name,
+ const void *value, size_t value_size);
+
+
+/**
+ * Get the first modifier of the environment.
+ */
+struct GNUNET_PSYC_Modifier *
+GNUNET_PSYC_env_head (const struct GNUNET_PSYC_Environment *env);
+
+
+
+/**
+ * Get the last modifier of the environment.
+ */
+struct GNUNET_PSYC_Modifier *
+GNUNET_PSYC_env_tail (const struct GNUNET_PSYC_Environment *env);
+
+
+/**
+ * Remove a modifier from the environment.
+ */
+void
+GNUNET_PSYC_env_remove (struct GNUNET_PSYC_Environment *env,
+ struct GNUNET_PSYC_Modifier *mod);
+
+
+/**
+ * Remove a modifier at the beginning of the environment.
+ */
+int
+GNUNET_PSYC_env_shift (struct GNUNET_PSYC_Environment *env,
+ enum GNUNET_PSYC_Operator *oper, const char **name,
+ const void **value, size_t *value_size);
+
+
+/**
+ * Iterator for modifiers in the environment.
+ *
+ * @param cls Closure.
+ * @param mod Modifier.
+ *
+ * @return #GNUNET_YES to continue iterating,
+ * #GNUNET_NO to stop.
+ */
+typedef int
+(*GNUNET_PSYC_Iterator) (void *cls, enum GNUNET_PSYC_Operator oper,
+ const char *name, const char *value,
+ uint32_t value_size);
+
+
+/**
+ * Iterate through all modifiers in the environment.
+ *
+ * @param env The environment.
+ * @param it Iterator.
+ * @param it_cls Closure for iterator.
+ */
+void
+GNUNET_PSYC_env_iterate (const struct GNUNET_PSYC_Environment *env,
+ GNUNET_PSYC_Iterator it, void *it_cls);
+
+
+/**
+ * Get the number of modifiers in the environment.
+ *
+ * @param env The environment.
+ *
+ * @return Number of modifiers.
+ */
+size_t
+GNUNET_PSYC_env_get_count (const struct GNUNET_PSYC_Environment *env);
+
+
+/**
+ * Destroy an environment.
+ *
+ * @param env The environment to destroy.
+ */
+void
+GNUNET_PSYC_env_destroy (struct GNUNET_PSYC_Environment *env);
+
+
+/**
+ * Get the type of variable.
+ *
+ * @param name Name of the variable.
+ *
+ * @return Variable type.
+ */
+enum GNUNET_PSYC_Type
+GNUNET_PSYC_var_get_type (char *name);
+
+
+/**
+ * Perform an operation on a variable.
+ *
+ * @param name Name of variable.
+ * @param current_value Current value of variable.
+ * @param current_value_size Size of @a current_value.
+ * @param oper Operator.
+ * @param args Arguments for the operation.
+ * @param args_size Size of @a args.
+ * @param return_value Return value.
+ * @param return_value_size Size of @a return_value.
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+int
+GNUNET_PSYC_operation (char *name, void *current_value, size_t current_value_size,
+ enum GNUNET_PSYC_Operator oper, void *args, size_t args_size,
+ void **return_value, size_t *return_value_size);
+
+
+/**
+ * Get the variable's value as an integer.
+ *
+ * @param size Size of value.
+ * @param value Raw value of variable.
+ * @param[out] number Value converted to a 64-bit integer.
+ *
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if an error occurred (e.g. the value is invalid).
+ */
+int
+GNUNET_PSYC_value_to_number (size_t size, const void *value, int64_t *number);
+
+
+/**
+ * Get the variable's value as a dictionary.
+ *
+ * @param size Size of value.
+ * @param value Raw value of variable.
+ * @param[out] dict A newly created hashmap holding the elements of the dictionary.
+ *
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if an error occurred (e.g. the value is invalid).
+ */
+int
+GNUNET_PSYC_value_to_dict (size_t size, const void *value, struct GNUNET_CONTAINER_MultiHashMap **dict);
+
+
+/**
+ * Create a PSYC variable value from an integer.
+ *
+ * @param number The number to convert.
+ * @param[out] value_size Size of returned value.
+ *
+ * @return A newly allocated value or NULL on error.
+ */
+void *
+GNUNET_PSYC_value_from_number (int64_t number, size_t *value_size);
+
+
+/**
+ * Create a PSYC variable value from a dictionary.
+ *
+ * @param dict The dict to convert.
+ * @param[out] value_size Size of returned value.
+ *
+ * @return A newly allocated value or NULL on error.
+ */
+void *
+GNUNET_PSYC_value_from_dict (struct GNUNET_CONTAINER_MultiHashMap *dict, size_t *value_size);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_PSYC_ENV_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_psyc_message.h b/src/include/gnunet_psyc_message.h
new file mode 100644
index 0000000..d0cf9cc
--- /dev/null
+++ b/src/include/gnunet_psyc_message.h
@@ -0,0 +1,278 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013 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 Gabor X Toth
+ *
+ * @file
+ * PSYC message utilities; receiving/transmitting/logging PSYC messages
+ *
+ * @defgroup psyc-util-message PSYC Utilities library: Messages
+ * Receiving, transmitting, logging PSYC messages.
+ * @{
+ */
+
+#ifndef GNUNET_PSYC_MESSAGE_H
+#define GNUNET_PSYC_MESSAGE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+
+#include "gnunet_util_lib.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_psyc_service.h"
+
+
+/**
+ * Create a PSYC message.
+ *
+ * @param method_name
+ * PSYC method for the message.
+ * @param env
+ * Environment for the message.
+ * @param data
+ * Data payload for the message.
+ * @param data_size
+ * Size of @a data.
+ *
+ * @return Message header with size information,
+ * followed by the message parts.
+ *
+ * FIXME: arg order
+ */
+struct GNUNET_PSYC_Message *
+GNUNET_PSYC_message_create (const char *method_name,
+ const struct GNUNET_PSYC_Environment *env,
+ const void *data,
+ size_t data_size);
+
+/**
+ * Parse PSYC message.
+ *
+ * @param msg
+ * The PSYC message to parse.
+ * @param env
+ * The environment for the message with a list of modifiers.
+ * @param[out] method_name
+ * Pointer to the method name inside @a pmsg.
+ * @param[out] data
+ * Pointer to data inside @a pmsg.
+ * @param[out] data_size
+ * Size of @data is written here.
+ *
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on parse error.
+ *
+ * FIXME: arg order
+ */
+int
+GNUNET_PSYC_message_parse (const struct GNUNET_PSYC_MessageHeader *msg,
+ const char **method_name,
+ struct GNUNET_PSYC_Environment *env,
+ const void **data,
+ uint16_t *data_size);
+
+
+void
+GNUNET_PSYC_log_message (enum GNUNET_ErrorType kind,
+ const struct GNUNET_MessageHeader *msg);
+
+
+struct GNUNET_PSYC_TransmitHandle;
+
+/**
+ * Create a transmission handle.
+ */
+struct GNUNET_PSYC_TransmitHandle *
+GNUNET_PSYC_transmit_create (struct GNUNET_MQ_Handle *mq);
+
+
+/**
+ * Destroy a transmission handle.
+ */
+void
+GNUNET_PSYC_transmit_destroy (struct GNUNET_PSYC_TransmitHandle *tmit);
+
+
+/**
+ * Transmit a message.
+ *
+ * @param tmit
+ * Transmission handle.
+ * @param method_name
+ * Which method should be invoked.
+ * @param env
+ * Environment for the message.
+ * Should stay available until the first call to notify_data.
+ * Can be NULL if there are no modifiers or @a notify_mod is
+ * provided instead.
+ * @param notify_mod
+ * Function to call to obtain modifiers.
+ * Can be NULL if there are no modifiers or @a env is provided instead.
+ * @param notify_data
+ * Function to call to obtain fragments of the data.
+ * @param notify_cls
+ * Closure for @a notify_mod and @a notify_data.
+ * @param flags
+ * Flags for the message being transmitted.
+ *
+ * @return #GNUNET_OK if the transmission was started.
+ * #GNUNET_SYSERR if another transmission is already going on.
+ */
+int
+GNUNET_PSYC_transmit_message (struct GNUNET_PSYC_TransmitHandle *tmit,
+ const char *method_name,
+ const struct GNUNET_PSYC_Environment *env,
+ GNUNET_PSYC_TransmitNotifyModifier notify_mod,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_cls,
+ uint32_t flags);
+
+
+/**
+ * Resume transmission.
+ *
+ * @param tmit Transmission handle.
+ */
+void
+GNUNET_PSYC_transmit_resume (struct GNUNET_PSYC_TransmitHandle *tmit);
+
+
+/**
+ * Abort transmission request.
+ *
+ * @param tmit Transmission handle.
+ */
+void
+GNUNET_PSYC_transmit_cancel (struct GNUNET_PSYC_TransmitHandle *tmit);
+
+
+/**
+ * Got acknowledgement of a transmitted message part, continue transmission.
+ *
+ * @param tmit Transmission handle.
+ */
+void
+GNUNET_PSYC_transmit_got_ack (struct GNUNET_PSYC_TransmitHandle *tmit);
+
+
+struct GNUNET_PSYC_ReceiveHandle;
+
+
+/**
+ * Create handle for receiving messages.
+ */
+struct GNUNET_PSYC_ReceiveHandle *
+GNUNET_PSYC_receive_create (GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ void *cb_cls);
+
+
+/**
+ * Destroy handle for receiving messages.
+ */
+void
+GNUNET_PSYC_receive_destroy (struct GNUNET_PSYC_ReceiveHandle *recv);
+
+
+/**
+ * Reset stored data related to the last received message.
+ */
+void
+GNUNET_PSYC_receive_reset (struct GNUNET_PSYC_ReceiveHandle *recv);
+
+
+/**
+ * Handle incoming PSYC message.
+ *
+ * @param recv
+ * Receive handle.
+ * @param msg
+ * The message.
+ *
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on receive error.
+ */
+int
+GNUNET_PSYC_receive_message (struct GNUNET_PSYC_ReceiveHandle *recv,
+ const struct GNUNET_PSYC_MessageHeader *msg);
+
+
+/**
+ * Check if @a data contains a series of valid message parts.
+ *
+ * @param data_size
+ * Size of @a data.
+ * @param data
+ * Data.
+ * @param[out] first_ptype
+ * Type of first message part.
+ * @param[out] last_ptype
+ * Type of last message part.
+ *
+ * @return Number of message parts found in @a data.
+ * or GNUNET_SYSERR if the message contains invalid parts.
+ */
+int
+GNUNET_PSYC_receive_check_parts (uint16_t data_size, const char *data,
+ uint16_t *first_ptype, uint16_t *last_ptype);
+
+
+/**
+ * Initialize PSYC message header.
+ */
+void
+GNUNET_PSYC_message_header_init (struct GNUNET_PSYC_MessageHeader *pmsg,
+ const struct GNUNET_MULTICAST_MessageHeader *mmsg,
+ uint32_t flags);
+
+
+/**
+ * Create a new PSYC message header from a multicast message for sending it to clients.
+ */
+struct GNUNET_PSYC_MessageHeader *
+GNUNET_PSYC_message_header_create (const struct GNUNET_MULTICAST_MessageHeader *mmsg,
+ uint32_t flags);
+
+
+/**
+ * Create a new PSYC message header from a PSYC message.
+ */
+struct GNUNET_PSYC_MessageHeader *
+GNUNET_PSYC_message_header_create_from_psyc (const struct GNUNET_PSYC_Message *msg);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_PSYC_MESSAGE_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_psyc_service.h b/src/include/gnunet_psyc_service.h
new file mode 100644
index 0000000..3a3131e
--- /dev/null
+++ b/src/include/gnunet_psyc_service.h
@@ -0,0 +1,1364 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013 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 Gabor X Toth
+ * @author Christian Grothoff
+ *
+ * @file
+ * PSYC service
+ *
+ * @defgroup psyc PSYC service
+ * Send/receive messages in PSYC channels and access the PSYC Store.
+ *
+ * Note that clients of this API are NOT expected to understand the PSYC message
+ * format, only the semantics! Parsing (and serializing) the PSYC stream format
+ * is done within the implementation of the libgnunetpsyc library, and this API
+ * deliberately exposes as little as possible of the actual data stream format
+ * to the application!
+ *
+ * NOTE:
+ * - this API does not know about PSYC's "root" and "places";
+ * there is no 'root' in GNUnet-PSYC as we're decentralized;
+ * 'places' and 'persons' are combined within the same
+ * abstraction, that of a "channel". Channels are identified
+ * and accessed in this API using a public/private key.
+ * Higher-level applications should use NAMES within GNS
+ * to obtain public keys, and the distinction between
+ * 'places' and 'persons' can then be made with the help
+ * of the naming system (and/or conventions).
+ * Channels are (as in PSYC) organized into a hierarchy; each
+ * channel master (the one with the private key) is then
+ * the operator of the multicast group (its Origin in
+ * the terminology of the multicast API).
+ * - The API supports passing large amounts of data using
+ * 'streaming' for the argument passed to a method. State
+ * and variables must fit into memory and cannot be streamed
+ * (thus, no passing of 4 GB of data in a variable;
+ * once we implement this, we might want to create a
+ * @c \#define for the maximum size of a variable).
+ * - PSYC defines standard variables, methods, etc. This
+ * library deliberately abstracts over all of these; a
+ * higher-level API should combine the naming system (GNS)
+ * and standard methods (_converse, _notice, _request,
+ * _warning, _error etc) and variables (_action, _color,
+ * _time, etc). However, this API does take over the
+ * routing variables, specifically '_context' (channel),
+ * and '_source'. We only kind-of support '_target', as
+ * the target is either everyone in the group or the
+ * origin, and never just a single member of the group;
+ * for such individual messages, an application needs to
+ * construct an 'inbox' channel where the master (only)
+ * receives messages (but never forwards; private responses
+ * would be transmitted by joining the senders 'inbox'
+ * channel -- or a inbox#bob subchannel). The
+ * goal for all of this is to keep the abstractions in this
+ * API minimal: interaction with multicast, try \& slice,
+ * state/variable/channel management. Higher-level
+ * operations belong elsewhere (so maybe this API should
+ * be called 'PSYC-low', whereas a higher-level API
+ * implementing defaults for standard methods and
+ * variables might be called 'PSYC-std' or 'PSYC-high'.
+ *
+ * In PSYC terminology this is simply called the "PSYC
+ * routing layer" and the abstractions, for instance in
+ * psyced, are quite similar. The higher one is called
+ * "PSYC entity layer." In the text rendering of the
+ * protocol the two are separated by an empty line. See
+ * http://about.psyc.eu/Spec:Packet and related. --lynX
+ *
+ * @{
+ */
+
+#ifndef GNUNET_PSYC_SERVICE_H
+#define GNUNET_PSYC_SERVICE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+#include "gnunet_util_lib.h"
+#include "gnunet_multicast_service.h"
+//Mingw work around
+#ifdef MINGW
+ # ifndef UINT64_MAX
+ # define UINT64_MAX 0xffffffffffffffffULL
+ # endif
+#endif
+
+/**
+ * Version number of GNUnet-PSYC API.
+ */
+#define GNUNET_PSYC_VERSION 0x00000000
+
+
+/**
+ * Policy flags for a channel.
+ */
+enum GNUNET_PSYC_ChannelFlags
+{
+ /**
+ * Admission must be confirmed by the master.
+ */
+ GNUNET_PSYC_CHANNEL_ADMISSION_CONTROL = 1 << 0,
+
+ /**
+ * Past messages are only available to slaves who were admitted at the time
+ * they were sent to the channel.
+ */
+ GNUNET_PSYC_CHANNEL_RESTRICTED_HISTORY = 1 << 1
+};
+
+
+/**
+ * PSYC channel policies.
+ */
+enum GNUNET_PSYC_Policy
+{
+ /**
+ * Anyone can join the channel, without announcing their presence;
+ * all messages are always public and can be distributed freely.
+ * Joins may be announced, but this is not required.
+ */
+ GNUNET_PSYC_CHANNEL_ANONYMOUS = 0,
+
+ /**
+ * The master must approve membership to the channel, messages must only be
+ * distributed to current channel slaves. This includes the channel
+ * state as well as transient messages.
+ */
+ GNUNET_PSYC_CHANNEL_PRIVATE
+ = GNUNET_PSYC_CHANNEL_ADMISSION_CONTROL
+ | GNUNET_PSYC_CHANNEL_RESTRICTED_HISTORY
+
+#if IDEAS_FOR_FUTURE
+ /**
+ * Anyone can freely join the channel (no approval required);
+ * however, messages must only be distributed to current channel
+ * slaves, so the master must still acknowledge that the slave
+ * joined before transient messages are delivered. As approval is
+ * guaranteed, the presistent channel state can be synchronized freely
+ * immediately, prior to master confirmation.
+ */
+ GNUNET_PSYC_CHANNEL_OPEN
+ = GNUNET_PSYC_CHANNEL_RESTRICTED_HISTORY,
+
+ /**
+ * The master must approve joins to the channel, but past messages can be
+ * freely distributed to slaves.
+ */
+ GNUNET_PSYC_CHANNEL_CLOSED
+ = GNUNET_PSYC_CHANNEL_ADMISSION_CONTROL,
+#endif
+};
+
+
+enum GNUNET_PSYC_MessageFlags
+{
+ /**
+ * Default / no flags.
+ */
+ GNUNET_PSYC_MESSAGE_DEFAULT = 0,
+
+ /**
+ * Historic message, retrieved from PSYCstore.
+ */
+ GNUNET_PSYC_MESSAGE_HISTORIC = 1 << 0,
+
+ /**
+ * Request from slave to master.
+ */
+ GNUNET_PSYC_MESSAGE_REQUEST = 1 << 1,
+
+ /**
+ * Message can be delivered out of order.
+ */
+ GNUNET_PSYC_MESSAGE_ORDER_ANY = 1 << 2
+};
+
+
+/**
+ * Values for the @a state_delta field of GNUNET_PSYC_MessageHeader.
+ */
+enum GNUNET_PSYC_StateDeltaValues
+{
+ GNUNET_PSYC_STATE_RESET = 0,
+
+ GNUNET_PSYC_STATE_NOT_MODIFIED = UINT64_MAX
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * A PSYC message.
+ *
+ * Used for single-fragment messages e.g. in a join request or response.
+ */
+struct GNUNET_PSYC_Message
+{
+ /**
+ * Message header with size and type information.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /* Followed by concatenated PSYC message parts:
+ * messages with GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_* types
+ */
+};
+
+
+/**
+ * Header of a PSYC message.
+ *
+ * The PSYC service adds this when delivering the message to local clients,
+ * not present on the multicast layer.
+ */
+struct GNUNET_PSYC_MessageHeader
+{
+ /**
+ * Generic message header with size and type information.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Flags for this message fragment.
+ *
+ * @see enum GNUNET_PSYC_MessageFlags
+ */
+ uint32_t flags GNUNET_PACKED;
+
+ /**
+ * Number of the message this message part belongs to.
+ * Monotonically increasing from 1.
+ */
+ uint64_t message_id GNUNET_PACKED;
+
+ /**
+ * Byte offset of this @e fragment of the @e message.
+ */
+ uint64_t fragment_offset GNUNET_PACKED;
+
+ /**
+ * Sending slave's public key.
+ * Not set if the message is from the master.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+
+ /* Followed by concatenated PSYC message parts:
+ * messages with GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_* types
+ */
+};
+
+
+/**
+ * The method of a message.
+ */
+struct GNUNET_PSYC_MessageMethod
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * OR'ed GNUNET_PSYC_MasterTransmitFlags
+ */
+ uint32_t flags GNUNET_PACKED;
+
+ /**
+ * Number of message IDs since the last message that contained state
+ * operations. @see enum GNUNET_PSYC_StateDeltaValues
+ */
+ uint64_t state_delta GNUNET_PACKED;
+
+ /* Followed by NUL-terminated method name. */
+};
+
+
+/**
+ * A modifier of a message.
+ */
+struct GNUNET_PSYC_MessageModifier
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Size of value.
+ */
+ uint32_t value_size GNUNET_PACKED;
+
+ /**
+ * Size of name, including NUL terminator.
+ */
+ uint16_t name_size GNUNET_PACKED;
+
+ /**
+ * enum GNUNET_PSYC_Operator
+ */
+ uint8_t oper;
+
+ /* Followed by NUL-terminated name, then the value. */
+};
+
+
+struct GNUNET_PSYC_CountersResultMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_RESULT_COUNTERS
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Status code for the operation.
+ */
+ uint32_t result_code GNUNET_PACKED;
+
+ /**
+ * Last message ID sent to the channel.
+ */
+ uint64_t max_message_id GNUNET_PACKED;
+};
+
+
+/**
+ * Join request sent to a PSYC master.
+ */
+struct GNUNET_PSYC_JoinRequestMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_MASTER_JOIN_REQUEST
+ */
+ struct GNUNET_MessageHeader header;
+ /**
+ * Public key of the joining slave.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+
+ /* Followed by struct GNUNET_MessageHeader join_request */
+};
+
+
+/**
+ * Join decision sent in reply to a join request.
+ */
+struct GNUNET_PSYC_JoinDecisionMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * #GNUNET_YES if the slave was admitted.
+ */
+ int32_t is_admitted;
+
+ /**
+ * Public key of the joining slave.
+ * Only set when the master is sending the decision,
+ * not set when a slave is receiving it.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+
+ /* Followed by struct GNUNET_MessageHeader join_response */
+};
+
+
+enum GNUNET_PSYC_HistoryReplayFlags
+{
+ /**
+ * Replay locally available messages.
+ */
+ GNUNET_PSYC_HISTORY_REPLAY_LOCAL = 0,
+
+ /**
+ * Replay messages from remote peers if not found locally.
+ */
+ GNUNET_PSYC_HISTORY_REPLAY_REMOTE = 1,
+};
+
+
+struct GNUNET_PSYC_HistoryRequestMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_HISTORY_REPLAY
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * @see enum GNUNET_PSYC_HistoryReplayFlags
+ */
+ uint32_t flags GNUNET_PACKED;
+
+ /**
+ * ID for this operation.
+ */
+ uint64_t op_id GNUNET_PACKED;
+
+ uint64_t start_message_id GNUNET_PACKED;
+
+ uint64_t end_message_id GNUNET_PACKED;
+
+ uint64_t message_limit GNUNET_PACKED;
+
+ /* Followed by NUL-terminated method name prefix. */
+};
+
+
+struct GNUNET_PSYC_StateRequestMessage
+{
+ /**
+ * Types:
+ * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET
+ * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET_PREFIX
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t reserved GNUNET_PACKED;
+
+ /**
+ * ID for this operation.
+ */
+ uint64_t op_id GNUNET_PACKED;
+
+ /* Followed by NUL-terminated name. */
+};
+
+
+/**** service -> library ****/
+
+
+/**
+ * Answer from service to client about last operation.
+ */
+struct GNUNET_PSYC_OperationResultMessage
+{
+ /**
+ * Types:
+ * - GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE
+ * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_RESULT
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t reserved GNUNET_PACKED;
+
+ /**
+ * Operation ID.
+ */
+ uint64_t op_id GNUNET_PACKED;
+
+ /**
+ * Status code for the operation.
+ */
+ uint64_t result_code GNUNET_PACKED;
+
+ /* Followed by:
+ * - on error: NUL-terminated error message
+ * - on success: one of the following message types
+ *
+ * For a STATE_RESULT, one of:
+ * - GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER
+ * - GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT
+ * - GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END
+ */
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+#define GNUNET_PSYC_MODIFIER_MAX_PAYLOAD \
+ GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \
+ - sizeof (struct GNUNET_PSYC_MessageModifier)
+
+#define GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD \
+ GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \
+ - sizeof (struct GNUNET_MessageHeader)
+
+#define GNUNET_PSYC_DATA_MAX_PAYLOAD \
+ GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \
+ - sizeof (struct GNUNET_MessageHeader)
+
+
+/**
+ * PSYC message part processing states.
+ */
+enum GNUNET_PSYC_MessageState
+{
+ GNUNET_PSYC_MESSAGE_STATE_START = 0,
+ GNUNET_PSYC_MESSAGE_STATE_HEADER = 1,
+ GNUNET_PSYC_MESSAGE_STATE_METHOD = 2,
+ GNUNET_PSYC_MESSAGE_STATE_MODIFIER = 3,
+ GNUNET_PSYC_MESSAGE_STATE_MOD_CONT = 4,
+ GNUNET_PSYC_MESSAGE_STATE_DATA = 5,
+ GNUNET_PSYC_MESSAGE_STATE_END = 6,
+ GNUNET_PSYC_MESSAGE_STATE_CANCEL = 7,
+ GNUNET_PSYC_MESSAGE_STATE_ERROR = 8,
+};
+
+
+/**
+ * Handle that identifies a join request.
+ *
+ * Used to match calls to #GNUNET_PSYC_JoinCallback to the
+ * corresponding calls to GNUNET_PSYC_join_decision().
+ */
+struct GNUNET_PSYC_JoinHandle;
+
+
+/**
+ * Method called from PSYC upon receiving a message.
+ *
+ * @param cls Closure.
+ * @param message_id Sequence number of the message.
+ * @param flags OR'ed GNUNET_PSYC_MessageFlags
+ * @param msg Message part, one of the following types:
+ */
+typedef void
+(*GNUNET_PSYC_MessageCallback) (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg);
+
+
+/**
+ * Method called from PSYC upon receiving part of a message.
+ *
+ * @param cls
+ * Closure.
+ * @param slave_pub_key
+ * Public key of the slave sending the message.
+ * Only set for channel master.
+ * @param message_id
+ * Sequence number of the message.
+ * @param flags
+ * OR'ed GNUNET_PSYC_MessageFlags
+ * @param fragment_offset
+ * Multicast message fragment offset.
+ * @param msg Message part, one of the following types:
+ * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_HEADER
+ * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD
+ * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER
+ * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT
+ * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA
+ * or NULL if an error occurred while receiving a message.
+ */
+typedef void
+(*GNUNET_PSYC_MessagePartCallback) (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg);
+
+
+/**
+ * Method called from PSYC upon receiving a join request.
+ *
+ * @param cls
+ * Closure.
+ * @param slave_pub_key
+ * Public key of the slave requesting join.
+ * @param join_msg
+ * Join message sent along with the request.
+ * @param jh
+ * Join handle to use with GNUNET_PSYC_join_decision()
+ */
+typedef void
+(*GNUNET_PSYC_JoinRequestCallback) (void *cls,
+ const struct GNUNET_PSYC_JoinRequestMessage *req,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ const struct GNUNET_PSYC_Message *join_msg,
+ struct GNUNET_PSYC_JoinHandle *jh);
+
+
+/**
+ * Function to call with the decision made for a join request.
+ *
+ * Must be called once and only once in response to an invocation of the
+ * #GNUNET_PSYC_JoinCallback.
+ *
+ * @param jh Join request handle.
+ * @param is_admitted
+ * #GNUNET_YES if the join is approved,
+ * #GNUNET_NO if it is disapproved,
+ * #GNUNET_SYSERR if we cannot answer the request.
+ * @param relay_count Number of relays given.
+ * @param relays Array of suggested peers that might be useful relays to use
+ * when joining the multicast group (essentially a list of peers that
+ * are already part of the multicast group and might thus be willing
+ * to help with routing). If empty, only this local peer (which must
+ * be the multicast origin) is a good candidate for building the
+ * multicast tree. Note that it is unnecessary to specify our own
+ * peer identity in this array.
+ * @param join_resp Application-dependent join response message to send along
+ * with the decision.
+ *
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR if @a join_resp is too large.
+ */
+int
+GNUNET_PSYC_join_decision (struct GNUNET_PSYC_JoinHandle *jh,
+ int is_admitted,
+ uint32_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_PSYC_Message *join_resp);
+
+
+/**
+ * Handle for the master of a PSYC channel.
+ */
+struct GNUNET_PSYC_Master;
+
+
+/**
+ * Function called once we are connected to the PSYC service
+ * and the channel master is started.
+ *
+ * Also called when we reconnected to the service
+ * after the connection closed unexpectedly.
+ *
+ * @param cls
+ * Closure.
+ * @param result
+ * #GNUNET_YES if there were already messages sent to the channel,
+ * #GNUNET_NO if the message history is empty,
+ * #GNUNET_SYSERR on error.
+ * @param max_message_id
+ * Last message ID sent to the channel.
+ */
+typedef void
+(*GNUNET_PSYC_MasterStartCallback) (void *cls, int result,
+ uint64_t max_message_id);
+
+
+/**
+ * Start a PSYC master channel.
+ *
+ * Will start a multicast group identified by the given ECC key. Messages
+ * received from group members will be given to the respective handler methods.
+ * If a new member wants to join a group, the "join" method handler will be
+ * invoked; the join handler must then generate a "join" message to approve the
+ * joining of the new member. The channel can also change group membership
+ * without explicit requests. Note that PSYC doesn't itself "understand" join
+ * or part messages, the respective methods must call other PSYC functions to
+ * inform PSYC about the meaning of the respective events.
+ *
+ * @param cfg Configuration to use (to connect to PSYC service).
+ * @param channel_key ECC key that will be used to sign messages for this
+ * PSYC session. The public key is used to identify the PSYC channel.
+ * Note that end-users will usually not use the private key directly, but
+ * rather look it up in GNS for places managed by other users, or select
+ * a file with the private key(s) when setting up their own channels
+ * FIXME: we'll likely want to use NOT the p521 curve here, but a cheaper
+ * one in the future.
+ * @param policy Channel policy specifying join and history restrictions.
+ * Used to automate join decisions.
+ * @param master_start_cb Function to invoke after the channel master started.
+ * @param join_request_cb Function to invoke when a slave wants to join.
+ * @param message_cb Function to invoke on message parts sent to the channel
+ * and received from slaves
+ * @param cls Closure for @a method and @a join_cb.
+ *
+ * @return Handle for the channel master, NULL on error.
+ */
+struct GNUNET_PSYC_Master *
+GNUNET_PSYC_master_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key,
+ enum GNUNET_PSYC_Policy policy,
+ GNUNET_PSYC_MasterStartCallback master_start_cb,
+ GNUNET_PSYC_JoinRequestCallback join_request_cb,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ void *cls);
+
+
+/**
+ * Function called to provide data for a transmission via PSYC.
+ *
+ * Note that returning #GNUNET_YES or #GNUNET_SYSERR (but not #GNUNET_NO)
+ * invalidates the respective transmission handle.
+ *
+ * @param cls Closure.
+ * @param[in,out] data_size Initially set to the number of bytes available in
+ * @a data, should be set to the number of bytes written to data.
+ * @param[out] data Where to write the body of the message to give to the
+ * method. The function must copy at most @a data_size bytes to @a data.
+ * @return #GNUNET_SYSERR on error (fatal, aborts transmission)
+ * #GNUNET_NO on success, if more data is to be transmitted later.
+ * Should be used if @a data_size was not big enough to take all the
+ * data. If 0 is returned in @a data_size the transmission is paused,
+ * and can be resumed with GNUNET_PSYC_master_transmit_resume().
+ * #GNUNET_YES if this completes the transmission (all data supplied)
+ */
+typedef int
+(*GNUNET_PSYC_TransmitNotifyData) (void *cls,
+ uint16_t *data_size,
+ void *data);
+
+/**
+ * Function called to provide a modifier for a transmission via PSYC.
+ *
+ * Note that returning #GNUNET_YES or #GNUNET_SYSERR (but not #GNUNET_NO)
+ * invalidates the respective transmission handle.
+ *
+ * @param cls Closure.
+ * @param[in,out] data_size Initially set to the number of bytes available in
+ * @a data, should be set to the number of bytes written to data.
+ * @param[out] data Where to write the modifier's name and value.
+ * The function must copy at most @a data_size bytes to @a data.
+ * When this callback is first called for a modifier, @a data should
+ * contain: "name\0value". If the whole value does not fit, subsequent
+ * calls to this function should write continuations of the value to
+ * @a data.
+ * @param[out] oper Where to write the operator of the modifier.
+ * Only needed during the first call to this callback at the beginning
+ * of the modifier. In case of subsequent calls asking for value
+ * continuations @a oper is set to #NULL.
+ * @param[out] full_value_size Where to write the full size of the value.
+ * Only needed during the first call to this callback at the beginning
+ * of the modifier. In case of subsequent calls asking for value
+ * continuations @a value_size is set to #NULL.
+ * @return #GNUNET_SYSERR on error (fatal, aborts transmission)
+ * #GNUNET_NO on success, if more data is to be transmitted later.
+ * Should be used if @a data_size was not big enough to take all the
+ * data for the modifier's value (the name must be always returned
+ * during the first call to this callback).
+ * If 0 is returned in @a data_size the transmission is paused,
+ * and can be resumed with GNUNET_PSYC_master_transmit_resume().
+ * #GNUNET_YES if this completes the modifier (the whole value is supplied).
+ */
+typedef int
+(*GNUNET_PSYC_TransmitNotifyModifier) (void *cls,
+ uint16_t *data_size,
+ void *data,
+ uint8_t *oper,
+ uint32_t *full_value_size);
+
+/**
+ * Flags for transmitting messages to a channel by the master.
+ */
+enum GNUNET_PSYC_MasterTransmitFlags
+{
+ GNUNET_PSYC_MASTER_TRANSMIT_NONE = 0,
+
+ /**
+ * Whether this message should reset the channel state,
+ * i.e. remove all previously stored state variables.
+ */
+
+ GNUNET_PSYC_MASTER_TRANSMIT_STATE_RESET = 1 << 0,
+
+ /**
+ * Whether this message contains any state modifiers.
+ */
+ GNUNET_PSYC_MASTER_TRANSMIT_STATE_MODIFY = 1 << 1,
+
+ /**
+ * Add PSYC header variable with the hash of the current channel state.
+ */
+ GNUNET_PSYC_MASTER_TRANSMIT_STATE_HASH = 1 << 2,
+
+ /**
+ * Whether we need to increment the group generation counter after
+ * transmitting this message.
+ */
+ GNUNET_PSYC_MASTER_TRANSMIT_INC_GROUP_GEN = 1 << 3
+};
+
+
+/**
+ * Handle for a pending PSYC transmission operation.
+ */
+struct GNUNET_PSYC_MasterTransmitHandle;
+
+
+/**
+ * Send a message to call a method to all members in the PSYC channel.
+ *
+ * @param master Handle to the PSYC channel.
+ * @param method_name Which method should be invoked.
+ * @param notify_mod Function to call to obtain modifiers.
+ * @param notify_data Function to call to obtain fragments of the data.
+ * @param notify_cls Closure for @a notify_mod and @a notify_data.
+ * @param flags Flags for the message being transmitted.
+ * @return Transmission handle, NULL on error (i.e. more than one request queued).
+ */
+struct GNUNET_PSYC_MasterTransmitHandle *
+GNUNET_PSYC_master_transmit (struct GNUNET_PSYC_Master *master,
+ const char *method_name,
+ GNUNET_PSYC_TransmitNotifyModifier notify_mod,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_cls,
+ enum GNUNET_PSYC_MasterTransmitFlags flags);
+
+
+/**
+ * Resume transmission to the channel.
+ *
+ * @param th Handle of the request that is being resumed.
+ */
+void
+GNUNET_PSYC_master_transmit_resume (struct GNUNET_PSYC_MasterTransmitHandle *th);
+
+
+/**
+ * Abort transmission request to channel.
+ *
+ * @param th Handle of the request that is being aborted.
+ */
+void
+GNUNET_PSYC_master_transmit_cancel (struct GNUNET_PSYC_MasterTransmitHandle *th);
+
+
+/**
+ * Relay a message
+ *
+ * @param master Handle to the PSYC channel.
+ * @param method_name Which method should be invoked.
+ * @param notify_mod Function to call to obtain modifiers.
+ * @param notify_data Function to call to obtain fragments of the data.
+ * @param notify_cls Closure for @a notify_mod and @a notify_data.
+ * @param flags Flags for the message being transmitted.
+ * @return Transmission handle, NULL on error (i.e. more than one request queued).
+ */
+struct GNUNET_PSYC_MasterTransmitHandle *
+GNUNET_PSYC_master_relay (struct GNUNET_PSYC_Master *master,
+ uint64_t message_id);
+
+
+/**
+ * Stop a PSYC master channel.
+ *
+ * @param master
+ * PSYC channel master to stop.
+ * @param keep_active
+ * Keep place active after last application disconnected.
+ * @param stop_cb
+ * Function called after the master stopped
+ * and disconnected from the psyc service.
+ * @param stop_cls
+ * Closure for @a part_cb.
+ */
+void
+GNUNET_PSYC_master_stop (struct GNUNET_PSYC_Master *master,
+ int keep_active,
+ GNUNET_ContinuationCallback stop_cb,
+ void *stop_cls);
+
+
+/**
+ * Handle for a PSYC channel slave.
+ */
+struct GNUNET_PSYC_Slave;
+
+
+/**
+ * Function called after the slave connected to the PSYC service.
+ *
+ * Also called when reconnected to the service
+ * after the connection closed unexpectedly.
+ *
+ * @param cls
+ * Closure.
+ * @param result
+ * #GNUNET_YES if there were already messages sent to the channel,
+ * #GNUNET_NO if the message history is empty,
+ * #GNUNET_SYSERR on error.
+ * @param max_message_id
+ * Last message ID sent to the channel.
+ */
+typedef void
+(*GNUNET_PSYC_SlaveConnectCallback) (void *cls, int result,
+ uint64_t max_message_id);
+
+
+/**
+ * Method called to inform about the decision in response to a join request.
+ *
+ * If @a is_admitted is not #GNUNET_YES, then sending messages to the channel is
+ * not possible, but earlier history can be still queried.
+ *
+ * @param cls Closure.
+ * @param is_admitted #GNUNET_YES or #GNUNET_NO or #GNUNET_SYSERR
+ * @param join_msg Application-dependent join message from the origin.
+ */
+typedef void
+(*GNUNET_PSYC_JoinDecisionCallback) (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn,
+ int is_admitted,
+ const struct GNUNET_PSYC_Message *join_msg);
+
+/**
+ * Flags for GNUNET_PSYC_slave_join()
+ */
+enum GNUNET_PSYC_SlaveJoinFlags
+{
+ GNUNET_PSYC_SLAVE_JOIN_NONE = 0,
+
+ /**
+ * Local join for history access, no network connection is established.
+ */
+ GNUNET_PSYC_SLAVE_JOIN_LOCAL = 1,
+};
+
+
+/**
+ * Join a PSYC channel.
+ *
+ * The entity joining is always the local peer. The user must immediately use
+ * the GNUNET_PSYC_slave_transmit() functions to transmit a @e join_msg to the
+ * channel; if the join request succeeds, the channel state (and @e recent
+ * method calls) will be replayed to the joining member. There is no explicit
+ * notification on failure (as the channel may simply take days to approve,
+ * and disapproval is simply being ignored).
+ *
+ * @param cfg
+ * Configuration to use.
+ * @param channel_pub_key
+ * ECC public key that identifies the channel we wish to join.
+ * @param slave_pub_key
+ * ECC private-public key pair that identifies the slave, and
+ * used by multicast to sign the join request and subsequent unicast
+ * requests sent to the master.
+ * @param flags
+ * Join flags.
+ * @param origin
+ * Peer identity of the origin.
+ * @param relay_count
+ * Number of peers in the @a relays array.
+ * @param relays
+ * Peer identities of members of the multicast group, which serve
+ * as relays and used to join the group at.
+ * @param message_cb
+ * Function to invoke on message fragments received from the channel.
+ * @param message_part_cb
+ * Function to invoke on message parts received from the channel.
+ * @param slave_connect_cb
+ * Function invoked once we have connected to the PSYC service.
+ * @param join_decision_cb
+ * Function invoked once we have received a join decision.
+ * @param cls
+ * Closure for @a message_cb and @a slave_joined_cb.
+ * @param join_msg
+ * Join message.
+ *
+ * @return Handle for the slave, NULL on error.
+ */
+struct GNUNET_PSYC_Slave *
+GNUNET_PSYC_slave_join (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_pub_key,
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_pub_key,
+ enum GNUNET_PSYC_SlaveJoinFlags flags,
+ const struct GNUNET_PeerIdentity *origin,
+ uint32_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_PSYC_SlaveConnectCallback slave_connect_cb,
+ GNUNET_PSYC_JoinDecisionCallback join_decision_cb,
+ void *cls,
+ const struct GNUNET_PSYC_Message *join_msg);
+
+
+/**
+ * Part a PSYC channel.
+ *
+ * Will terminate the connection to the PSYC service. Polite clients should
+ * first explicitly send a part request (via GNUNET_PSYC_slave_transmit()).
+ *
+ * @param slave
+ * Slave handle.
+ * @param keep_active
+ * Keep place active after last application disconnected.
+ * @param part_cb
+ * Function called after the slave parted the channel
+ * and disconnected from the psyc service.
+ * @param part_cls
+ * Closure for @a part_cb.
+ */
+void
+GNUNET_PSYC_slave_part (struct GNUNET_PSYC_Slave *slave,
+ int keep_active,
+ GNUNET_ContinuationCallback part_cb,
+ void *part_cls);
+
+
+/**
+ * Flags for transmitting messages to the channel master by a slave.
+ */
+enum GNUNET_PSYC_SlaveTransmitFlags
+{
+ GNUNET_PSYC_SLAVE_TRANSMIT_NONE = 0
+};
+
+
+/**
+ * Handle for a pending PSYC transmission operation.
+ */
+struct GNUNET_PSYC_SlaveTransmitHandle;
+
+
+/**
+ * Request a message to be sent to the channel master.
+ *
+ * @param slave Slave handle.
+ * @param method_name Which (PSYC) method should be invoked (on host).
+ * @param notify_mod Function to call to obtain modifiers.
+ * @param notify_data Function to call to obtain fragments of the data.
+ * @param notify_cls Closure for @a notify.
+ * @param flags Flags for the message being transmitted.
+ * @return Transmission handle, NULL on error (i.e. more than one request queued).
+ */
+struct GNUNET_PSYC_SlaveTransmitHandle *
+GNUNET_PSYC_slave_transmit (struct GNUNET_PSYC_Slave *slave,
+ const char *method_name,
+ GNUNET_PSYC_TransmitNotifyModifier notify_mod,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_cls,
+ enum GNUNET_PSYC_SlaveTransmitFlags flags);
+
+
+/**
+ * Resume transmission to the master.
+ *
+ * @param th Handle of the request that is being resumed.
+ */
+void
+GNUNET_PSYC_slave_transmit_resume (struct GNUNET_PSYC_SlaveTransmitHandle *th);
+
+
+/**
+ * Abort transmission request to master.
+ *
+ * @param th Handle of the request that is being aborted.
+ */
+void
+GNUNET_PSYC_slave_transmit_cancel (struct GNUNET_PSYC_SlaveTransmitHandle *th);
+
+
+/**
+ * Handle to access PSYC channel operations for both the master and slaves.
+ */
+struct GNUNET_PSYC_Channel;
+
+
+/**
+ * Convert a channel @a master to a @e channel handle to access the @e channel
+ * APIs.
+ *
+ * @param master Channel master handle.
+ * @return Channel handle, valid for as long as @a master is valid.
+ */
+struct GNUNET_PSYC_Channel *
+GNUNET_PSYC_master_get_channel (struct GNUNET_PSYC_Master *master);
+
+
+/**
+ * Convert @a slave to a @e channel handle to access the @e channel APIs.
+ *
+ * @param slave Slave handle.
+ * @return Channel handle, valid for as long as @a slave is valid.
+ */
+struct GNUNET_PSYC_Channel *
+GNUNET_PSYC_slave_get_channel (struct GNUNET_PSYC_Slave *slave);
+
+
+/**
+ * Add a slave to the channel's membership list.
+ *
+ * Note that this will NOT generate any PSYC traffic, it will merely update the
+ * local database to modify how we react to <em>membership test</em> queries.
+ * The channel master still needs to explicitly transmit a @e join message to
+ * notify other channel members and they then also must still call this function
+ * in their respective methods handling the @e join message. This way, how @e
+ * join and @e part operations are exactly implemented is still up to the
+ * application; for example, there might be a @e part_all method to kick out
+ * everyone.
+ *
+ * Note that channel slaves are explicitly trusted to execute such methods
+ * correctly; not doing so correctly will result in either denying other slaves
+ * access or offering access to channel data to non-members.
+ *
+ * @param channel
+ * Channel handle.
+ * @param slave_pub_key
+ * Identity of channel slave to add.
+ * @param announced_at
+ * ID of the message that announced the membership change.
+ * @param effective_since
+ * Addition of slave is in effect since this message ID.
+ * @param result_cb
+ * Function to call with the result of the operation.
+ * The @e result_code argument is #GNUNET_OK on success, or
+ * #GNUNET_SYSERR on error. In case of an error, the @e data argument
+ * can contain an optional error message.
+ * @param cls
+ * Closure for @a result_cb.
+ */
+void
+GNUNET_PSYC_channel_slave_add (struct GNUNET_PSYC_Channel *channel,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ uint64_t announced_at,
+ uint64_t effective_since,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Remove a slave from the channel's membership list.
+ *
+ * Note that this will NOT generate any PSYC traffic, it will merely update the
+ * local database to modify how we react to <em>membership test</em> queries.
+ * The channel master still needs to explicitly transmit a @e part message to
+ * notify other channel members and they then also must still call this function
+ * in their respective methods handling the @e part message. This way, how
+ * @e join and @e part operations are exactly implemented is still up to the
+ * application; for example, there might be a @e part_all message to kick out
+ * everyone.
+ *
+ * Note that channel members are explicitly trusted to perform these
+ * operations correctly; not doing so correctly will result in either
+ * denying members access or offering access to channel data to
+ * non-members.
+ *
+ * @param channel
+ * Channel handle.
+ * @param slave_pub_key
+ * Identity of channel slave to remove.
+ * @param announced_at
+ * ID of the message that announced the membership change.
+ * @param result_cb
+ * Function to call with the result of the operation.
+ * The @e result_code argument is #GNUNET_OK on success, or
+ * #GNUNET_SYSERR on error. In case of an error, the @e data argument
+ * can contain an optional error message.
+ * @param cls
+ * Closure for @a result_cb.
+ */
+void
+GNUNET_PSYC_channel_slave_remove (struct GNUNET_PSYC_Channel *channel,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey
+ *slave_pub_key,
+ uint64_t announced_at,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * History request handle.
+ */
+struct GNUNET_PSYC_HistoryRequest;
+
+
+/**
+ * Request to replay a part of the message history of the channel.
+ *
+ * Historic messages (but NOT the state at the time) will be replayed (given to
+ * the normal method handlers) if available and if access is permitted.
+ *
+ * @param channel
+ * Which channel should be replayed?
+ * @param start_message_id
+ * Earliest interesting point in history.
+ * @param end_message_id
+ * Last (inclusive) interesting point in history.
+ * @param method_prefix
+ * Retrieve only messages with a matching method prefix.
+ * @param flags
+ * OR'ed enum GNUNET_PSYC_HistoryReplayFlags
+ * @param result_cb
+ * Function to call when the requested history has been fully replayed.
+ * Once this function has been called, the client must not call
+ * GNUNET_PSYC_channel_history_replay_cancel() anymore.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle to cancel history replay operation.
+ */
+struct GNUNET_PSYC_HistoryRequest *
+GNUNET_PSYC_channel_history_replay (struct GNUNET_PSYC_Channel *channel,
+ uint64_t start_message_id,
+ uint64_t end_message_id,
+ const char *method_prefix,
+ uint32_t flags,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Request to replay the latest messages from the message history of the channel.
+ *
+ * Historic messages (but NOT the state at the time) will be replayed (given to
+ * the normal method handlers) if available and if access is permitted.
+ *
+ * @param channel
+ * Which channel should be replayed?
+ * @param message_limit
+ * Maximum number of messages to replay.
+ * @param flags
+ * OR'ed enum GNUNET_PSYC_HistoryReplayFlags
+ * @param finish_cb
+ * Function to call when the requested history has been fully replayed
+ * (counting message IDs might not suffice, as some messages might be
+ * secret and thus the listener would not know the story is finished
+ * without being told explicitly)o once this function has been called, the
+ * client must not call GNUNET_PSYC_channel_history_replay_cancel() anymore.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle to cancel history replay operation.
+ */
+struct GNUNET_PSYC_HistoryRequest *
+GNUNET_PSYC_channel_history_replay_latest (struct GNUNET_PSYC_Channel *channel,
+ uint64_t message_limit,
+ const char *method_prefix,
+ uint32_t flags,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+void
+GNUNET_PSYC_channel_history_replay_cancel (struct GNUNET_PSYC_Channel *channel,
+ struct GNUNET_PSYC_HistoryRequest *hr);
+
+
+/**
+ * Function called to inform a member about stored state values for a channel.
+ *
+ * If @a full_value_size > value_size then this function is called multiple
+ * times until the whole value arrived.
+ *
+ * @param cls
+ * Closure.
+ * @param name
+ * Name of the state variable.
+ * NULL if there are no more state variables to be returned.
+ * @param value
+ * Value of the state variable.
+ * @param value_size
+ * Number of bytes in @a value.
+ * @param full_value_size
+ * Number of bytes in the full value, including continuations.
+ * Only set for the first part of a variable,
+ * in case of a continuation it is 0.
+ */
+typedef void
+(*GNUNET_PSYC_StateVarCallback) (void *cls,
+ const struct GNUNET_MessageHeader *mod,
+ const char *name,
+ const void *value,
+ uint32_t value_size,
+ uint32_t full_value_size);
+
+
+/**
+ * State request handle.
+ */
+struct GNUNET_PSYC_StateRequest;
+
+
+/**
+ * Retrieve the best matching channel state variable.
+ *
+ * If the requested variable name is not present in the state, the nearest
+ * less-specific name is matched; for example, requesting "_a_b" will match "_a"
+ * if "_a_b" does not exist.
+ *
+ * @param channel
+ * Channel handle.
+ * @param full_name
+ * Full name of the requested variable.
+ * The actual variable returned might have a shorter name.
+ * @param var_cb
+ * Function called once when a matching state variable is found.
+ * Not called if there's no matching state variable.
+ * @param result_cb
+ * Function called after the operation finished.
+ * (i.e. all state variables have been returned via @a state_cb)
+ * @param cls
+ * Closure for the callbacks.
+ */
+struct GNUNET_PSYC_StateRequest *
+GNUNET_PSYC_channel_state_get (struct GNUNET_PSYC_Channel *channel,
+ const char *full_name,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Return all channel state variables whose name matches a given prefix.
+ *
+ * A name matches if it starts with the given @a name_prefix, thus requesting
+ * the empty prefix ("") will match all values; requesting "_a_b" will also
+ * return values stored under "_a_b_c".
+ *
+ * The @a state_cb is invoked on all matching state variables asynchronously, as
+ * the state is stored in and retrieved from the PSYCstore,
+ *
+ * @param channel
+ * Channel handle.
+ * @param name_prefix
+ * Prefix of the state variable name to match.
+ * @param var_cb
+ * Function called once when a matching state variable is found.
+ * Not called if there's no matching state variable.
+ * @param result_cb
+ * Function called after the operation finished.
+ * (i.e. all state variables have been returned via @a state_cb)
+ * @param cls
+ * Closure for the callbacks.
+ */
+struct GNUNET_PSYC_StateRequest *
+GNUNET_PSYC_channel_state_get_prefix (struct GNUNET_PSYC_Channel *channel,
+ const char *name_prefix,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+/**
+ * Cancel a state request operation.
+ *
+ * @param sr
+ * Handle for the operation to cancel.
+ */
+void
+GNUNET_PSYC_channel_state_get_cancel (struct GNUNET_PSYC_StateRequest *sr);
+
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_PSYC_SERVICE_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_psyc_slicer.h b/src/include/gnunet_psyc_slicer.h
new file mode 100644
index 0000000..87f66d7
--- /dev/null
+++ b/src/include/gnunet_psyc_slicer.h
@@ -0,0 +1,378 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2013 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 Gabor X Toth
+ * @author Christian Grothoff
+ *
+ * @file
+ * PSYC Slicer library
+ *
+ * @defgroup psyc-util-slicer PSYC Utilities library: Slicer
+ * Try-and-slice processing of PSYC method names and environment.
+ * @{
+ */
+
+#ifndef GNUNET_PSYC_SLICER_H
+#define GNUNET_PSYC_SLICER_H
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+#include "gnunet_util_lib.h"
+
+
+/**
+ * Handle to an implementation of try-and-slice.
+ */
+struct GNUNET_PSYC_Slicer;
+
+
+/**
+ * Function called upon receiving a message indicating a call to a @e method.
+ *
+ * This function is called one or more times for each message until all data
+ * fragments arrive from the network.
+ *
+ * @param cls
+ * Closure.
+ * @param msg
+ * Message part, as it arrived from the network.
+ * @param message_id
+ * Message counter, monotonically increasing from 1.
+ * @param flags
+ * OR'ed GNUNET_PSYC_MessageFlags
+ * @param fragment_offset
+ * Multicast message fragment offset.
+ * @param tmit_flags
+ * OR'ed GNUNET_PSYC_MasterTransmitFlags
+ * @param nym
+ * The sender of the message.
+ * Can be NULL if the message is not connected to a pseudonym.
+ * @param method_name
+ * Original method name from PSYC.
+ * May be more specific than the registered method name due to
+ * try-and-slice matching.
+ */
+typedef void
+(*GNUNET_PSYC_MethodCallback) (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_PSYC_MessageMethod *meth,
+ uint64_t message_id,
+ const char *method_name);
+
+
+/**
+ * Function called upon receiving a modifier of a message.
+ *
+ * @param cls
+ * Closure.
+ * @param message_id
+ * Message ID this data fragment belongs to.
+ * @param flags
+ * OR'ed GNUNET_PSYC_MessageFlags
+ * @param fragment_offset
+ * Multicast message fragment offset.
+ * @param msg
+ * Message part, as it arrived from the network.
+ * @param oper
+ * Operation to perform.
+ * 0 in case of a modifier continuation.
+ * @param name
+ * Name of the modifier.
+ * NULL in case of a modifier continuation.
+ * @param value
+ * Value of the modifier.
+ * @param value_size
+ * Size of @value.
+ */
+typedef void
+(*GNUNET_PSYC_ModifierCallback) (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg,
+ uint64_t message_id,
+ enum GNUNET_PSYC_Operator oper,
+ const char *name,
+ const void *value,
+ uint16_t value_size,
+ uint16_t full_value_size);
+
+
+/**
+ * Function called upon receiving a data fragment of a message.
+ *
+ * @param cls
+ * Closure.
+ * @param msg
+ * Message part, as it arrived from the network.
+ * @param message_id
+ * Message ID this data fragment belongs to.
+ * @param flags
+ * OR'ed GNUNET_PSYC_MessageFlags
+ * @param fragment_offset
+ * Multicast message fragment offset.
+ * @param data
+ * Data stream given to the method.
+ * @param data_size
+ * Number of bytes in @a data.
+ * @param end
+ * End of message?
+ * #GNUNET_NO if there are further fragments,
+ * #GNUNET_YES if this is the last fragment,
+ * #GNUNET_SYSERR indicates the message was cancelled by the sender.
+ */
+typedef void
+(*GNUNET_PSYC_DataCallback) (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg,
+ uint64_t message_id,
+ const void *data,
+ uint16_t data_size);
+
+
+/**
+ * End of message.
+ *
+ * @param cls
+ * Closure.
+ * @param msg
+ * Message part, as it arrived from the network.
+ * @param message_id
+ * Message ID this data fragment belongs to.
+ * @param flags
+ * OR'ed GNUNET_PSYC_MessageFlags
+ * @param fragment_offset
+ * Multicast message fragment offset.
+ * @param cancelled
+ * #GNUNET_YES if the message was cancelled,
+ * #GNUNET_NO if the message is complete.
+ */
+typedef void
+(*GNUNET_PSYC_EndOfMessageCallback) (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg,
+ uint64_t message_id,
+ uint8_t is_cancelled);
+
+
+/**
+ * Create a try-and-slice instance.
+ *
+ * A slicer processes incoming messages and notifies callbacks about matching
+ * methods or modifiers encountered.
+ *
+ * @return A new try-and-slice construct.
+ */
+struct GNUNET_PSYC_Slicer *
+GNUNET_PSYC_slicer_create (void);
+
+
+/**
+ * Add a method to the try-and-slice instance.
+ *
+ * The callbacks are called for messages with a matching @a method_name prefix.
+ *
+ * @param slicer
+ * The try-and-slice instance to extend.
+ * @param method_name
+ * Name of the given method, use empty string to match all.
+ * @param method_cb
+ * Method handler invoked upon a matching message.
+ * @param modifier_cb
+ * Modifier handler, invoked after @a method_cb
+ * for each modifier in the message.
+ * @param data_cb
+ * Data handler, invoked after @a modifier_cb for each data fragment.
+ * @param eom_cb
+ * Invoked upon reaching the end of a matching message.
+ * @param cls
+ * Closure for the callbacks.
+ */
+void
+GNUNET_PSYC_slicer_method_add (struct GNUNET_PSYC_Slicer *slicer,
+ const char *method_name,
+ GNUNET_PSYC_MessageCallback msg_cb,
+ GNUNET_PSYC_MethodCallback method_cb,
+ GNUNET_PSYC_ModifierCallback modifier_cb,
+ GNUNET_PSYC_DataCallback data_cb,
+ GNUNET_PSYC_EndOfMessageCallback eom_cb,
+ void *cls);
+
+/**
+ * Remove a registered method from the try-and-slice instance.
+ *
+ * Removes one matching handler registered with the given
+ * @a method_name and callbacks.
+ *
+ * @param slicer
+ * The try-and-slice instance.
+ * @param method_name
+ * Name of the method to remove.
+ * @param method_cb
+ * Only remove matching method handler, or NULL.
+ * @param modifier_cb
+ * Only remove matching modifier handler, or NULL.
+ * @param data_cb
+ * Only remove matching data handler, or NULL.
+ * @param eom_cb
+ * Only remove matching End of Message handler, or NULL.
+ *
+ * @return #GNUNET_OK if a method handler was removed,
+ * #GNUNET_NO if no handler matched the given method name and callbacks.
+ */
+int
+GNUNET_PSYC_slicer_method_remove (struct GNUNET_PSYC_Slicer *slicer,
+ const char *method_name,
+ GNUNET_PSYC_MessageCallback msg_cb,
+ GNUNET_PSYC_MethodCallback method_cb,
+ GNUNET_PSYC_ModifierCallback modifier_cb,
+ GNUNET_PSYC_DataCallback data_cb,
+ GNUNET_PSYC_EndOfMessageCallback eom_cb);
+
+
+/**
+ * Watch a place for changed objects.
+ *
+ * @param slicer
+ * The try-and-slice instance.
+ * @param object_filter
+ * Object prefix to match.
+ * @param modifier_cb
+ * Function to call when encountering a state modifier.
+ * @param cls
+ * Closure for callback.
+ */
+void
+GNUNET_PSYC_slicer_modifier_add (struct GNUNET_PSYC_Slicer *slicer,
+ const char *object_filter,
+ GNUNET_PSYC_ModifierCallback modifier_cb,
+ void *cls);
+
+
+/**
+ * Remove a registered modifier from the try-and-slice instance.
+ *
+ * Removes one matching handler registered with the given
+ * @a object_filter and callback.
+ *
+ * @param slicer
+ * The try-and-slice instance.
+ * @param object_filter
+ * Object prefix to match.
+ * @param modifier_cb
+ * Function to call when encountering a state modifier changes.
+ */
+int
+GNUNET_PSYC_slicer_modifier_remove (struct GNUNET_PSYC_Slicer *slicer,
+ const char *object_filter,
+ GNUNET_PSYC_ModifierCallback modifier_cb);
+
+
+/**
+ * Process an incoming message and call matching handlers.
+ *
+ * @param slicer
+ * The slicer to use.
+ * @param msg
+ * The message as it arrived from the network.
+ */
+void
+GNUNET_PSYC_slicer_message (struct GNUNET_PSYC_Slicer *slicer,
+ const struct GNUNET_PSYC_MessageHeader *msg);
+
+
+/**
+ * Process an incoming message part and call matching handlers.
+ *
+ * @param slicer
+ * The slicer to use.
+ * @param message_id
+ * ID of the message.
+ * @param flags
+ * Flags for the message.
+ * @see enum GNUNET_PSYC_MessageFlags
+ * @param fragment offset
+ * Fragment offset of the message.
+ * @param msg
+ * The message part as it arrived from the network.
+ */
+void
+GNUNET_PSYC_slicer_message_part (struct GNUNET_PSYC_Slicer *slicer,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg);
+
+
+/**
+ * Remove all registered method handlers.
+ *
+ * @param slicer
+ * Slicer to clear.
+ */
+void
+GNUNET_PSYC_slicer_method_clear (struct GNUNET_PSYC_Slicer *slicer);
+
+
+/**
+ * Remove all registered modifier handlers.
+ *
+ * @param slicer
+ * Slicer to clear.
+ */
+void
+GNUNET_PSYC_slicer_modifier_clear (struct GNUNET_PSYC_Slicer *slicer);
+
+
+/**
+ * Remove all registered method & modifier handlers.
+ *
+ * @param slicer
+ * Slicer to clear.
+ */
+void
+GNUNET_PSYC_slicer_clear (struct GNUNET_PSYC_Slicer *slicer);
+
+
+/**
+ * Destroy a given try-and-slice instance.
+ *
+ * @param slicer
+ * Slicer to destroy
+ */
+void
+GNUNET_PSYC_slicer_destroy (struct GNUNET_PSYC_Slicer *slicer);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_PSYC_SLICER_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_psyc_util_lib.h b/src/include/gnunet_psyc_util_lib.h
new file mode 100644
index 0000000..57eec65
--- /dev/null
+++ b/src/include/gnunet_psyc_util_lib.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013 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 Gabor X Toth
+ *
+ * @file
+ * PSYC utilities: messages, environment, slicer
+ */
+
+#ifndef GNUNET_PSYC_UTIL_LIB_H
+#define GNUNET_PSYC_UTIL_LIB_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+
+#include "gnunet_psyc_env.h"
+#include "gnunet_psyc_message.h"
+#include "gnunet_psyc_slicer.h"
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_PSYC_UTIL_LIB_H */
+#endif
diff --git a/src/include/gnunet_psycstore_plugin.h b/src/include/gnunet_psycstore_plugin.h
new file mode 100644
index 0000000..fac549f
--- /dev/null
+++ b/src/include/gnunet_psycstore_plugin.h
@@ -0,0 +1,383 @@
+/*
+ This file is part of GNUnet
+ Copyright (C) 2013 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 Gabor X Toth
+ *
+ * @file
+ * Plugin API for the PSYCstore database backend
+ *
+ * @defgroup psycstore-plugin PSYC Store plugin API
+ * Plugin API for the PSYC Store database backend
+ * @{
+ */
+#ifndef GNUNET_PSYCSTORE_PLUGIN_H
+#define GNUNET_PSYCSTORE_PLUGIN_H
+
+#include "gnunet_util_lib.h"
+#include "gnunet_psycstore_service.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+
+/**
+ * Struct returned by the initialization function of the plugin.
+ */
+struct GNUNET_PSYCSTORE_PluginFunctions
+{
+
+ /**
+ * Closure to pass to all plugin functions.
+ */
+ void *cls;
+
+ /**
+ * Store join/leave events for a PSYC channel in order to be able to answer
+ * membership test queries later.
+ *
+ * @see GNUNET_PSYCSTORE_membership_store()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*membership_store) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ int did_join,
+ uint64_t announced_at,
+ uint64_t effective_since,
+ uint64_t group_generation);
+
+ /**
+ * Test if a member was admitted to the channel at the given message ID.
+ *
+ * @see GNUNET_PSYCSTORE_membership_test()
+ *
+ * @return #GNUNET_YES if the member was admitted, #GNUNET_NO if not,
+ * #GNUNET_SYSERR if there was en error.
+ */
+ int
+ (*membership_test) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t message_id);
+
+ /**
+ * Store a message fragment sent to a channel.
+ *
+ * @see GNUNET_PSYCSTORE_fragment_store()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*fragment_store) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_MULTICAST_MessageHeader *message,
+ uint32_t psycstore_flags);
+
+ /**
+ * Set additional flags for a given message.
+ *
+ * They are OR'd with any existing flags set.
+ *
+ * @param cls Closure.
+ * @param channel_key Public key of the channel.
+ * @param message_id ID of the message.
+ * @param psycstore_flags OR'd GNUNET_PSYCSTORE_MessageFlags.
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*message_add_flags) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t message_id,
+ uint32_t psycstore_flags);
+
+ /**
+ * Retrieve a message fragment range by fragment ID.
+ *
+ * @see GNUNET_PSYCSTORE_fragment_get()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*fragment_get) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t first_fragment_id,
+ uint64_t last_fragment_id,
+ uint64_t *returned_fragments,
+ GNUNET_PSYCSTORE_FragmentCallback cb,
+ void *cb_cls);
+
+ /**
+ * Retrieve latest message fragments.
+ *
+ * @see GNUNET_PSYCSTORE_fragment_get()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*fragment_get_latest) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t fragment_limit,
+ uint64_t *returned_fragments,
+ GNUNET_PSYCSTORE_FragmentCallback cb,
+ void *cb_cls);
+
+ /**
+ * Retrieve all fragments of a message ID range.
+ *
+ * @see GNUNET_PSYCSTORE_message_get()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*message_get) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t first_fragment_id,
+ uint64_t last_fragment_id,
+ uint64_t fragment_limit,
+ uint64_t *returned_fragments,
+ GNUNET_PSYCSTORE_FragmentCallback cb,
+ void *cb_cls);
+
+ /**
+ * Retrieve all fragments of the latest messages.
+ *
+ * @see GNUNET_PSYCSTORE_message_get()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*message_get_latest) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t fragment_limit,
+ uint64_t *returned_fragments,
+ GNUNET_PSYCSTORE_FragmentCallback cb,
+ void *cb_cls);
+
+ /**
+ * Retrieve a fragment of message specified by its message ID and fragment
+ * offset.
+ *
+ * @see GNUNET_PSYCSTORE_message_get_fragment()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*message_get_fragment) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ GNUNET_PSYCSTORE_FragmentCallback cb,
+ void *cb_cls);
+
+ /**
+ * Retrieve the max. values of message counters for a channel.
+ *
+ * @see GNUNET_PSYCSTORE_counters_get()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*counters_message_get) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t *max_fragment_id,
+ uint64_t *max_message_id,
+ uint64_t *max_group_generation);
+
+ /**
+ * Retrieve the max. values of state counters for a channel.
+ *
+ * @see GNUNET_PSYCSTORE_counters_get()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*counters_state_get) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t *max_state_message_id);
+
+
+ /**
+ * Begin modifying current state.
+ *
+ * @see GNUNET_PSYCSTORE_state_modify()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_modify_begin) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t message_id, uint64_t state_delta);
+
+ /**
+ * Set the current value of a state variable.
+ *
+ * The state modification process is started with state_modify_begin(),
+ * which is followed by one or more calls to this function,
+ * and finished with state_modify_end().
+ *
+ * @see GNUNET_PSYCSTORE_state_modify()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_modify_op) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ enum GNUNET_PSYC_Operator op,
+ const char *name, const void *value, size_t value_size);
+
+
+ /**
+ * End modifying current state.
+ *
+ * @see GNUNET_PSYCSTORE_state_modify()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_modify_end) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t message_id);
+
+
+ /**
+ * Begin synchronizing state.
+ *
+ * @see GNUNET_PSYCSTORE_state_sync()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_sync_begin) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key);
+
+ /**
+ * Assign value of a state variable while synchronizing state.
+ *
+ * The state synchronization process is started with state_sync_begin(),
+ * which is followed by one or more calls to this function,
+ * and finished using state_sync_end().
+ *
+ * @see GNUNET_PSYCSTORE_state_sync()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_sync_assign) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const char *name, const void *value, size_t value_size);
+
+
+ /**
+ * End synchronizing state.
+ *
+ * @see GNUNET_PSYCSTORE_state_sync()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_sync_end) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t max_state_message_id,
+ uint64_t state_hash_message_id);
+
+
+ /**
+ * Reset the state of a channel.
+ *
+ * Delete all state variables stored for the given channel.
+ *
+ * @see GNUNET_PSYCSTORE_state_reset()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_reset) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key);
+
+ /**
+ * Update signed state values from the current ones.
+ *
+ * Sets value_signed = value_current for each variable for the given channel.
+ */
+ int
+ (*state_update_signed) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key);
+
+
+ /**
+ * Retrieve a state variable by name (exact match).
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_get) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const char *name,
+ GNUNET_PSYCSTORE_StateCallback cb,
+ void *cb_cls);
+
+ /**
+ * Retrieve all state variables for a channel with the given prefix.
+ *
+ * @see GNUNET_PSYCSTORE_state_get_prefix()
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_get_prefix) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const char *name,
+ GNUNET_PSYCSTORE_StateCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Retrieve all signed state variables for a channel.
+ *
+ * @return #GNUNET_OK on success, else #GNUNET_SYSERR
+ */
+ int
+ (*state_get_signed) (void *cls,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ GNUNET_PSYCSTORE_StateCallback cb,
+ void *cb_cls);
+
+};
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_psycstore_service.h b/src/include/gnunet_psycstore_service.h
new file mode 100644
index 0000000..92516f4
--- /dev/null
+++ b/src/include/gnunet_psycstore_service.h
@@ -0,0 +1,701 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2013 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 Gabor X Toth
+ * @author Christian Grothoff
+ *
+ * @file
+ * PSYCstore service; implements persistent storage for the PSYC service
+ *
+ * @defgroup psycstore PSYC Store service
+ * Persistent storage for the PSYC service.
+ * @{
+ */
+#ifndef GNUNET_PSYCSTORE_SERVICE_H
+#define GNUNET_PSYCSTORE_SERVICE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+#include "gnunet_util_lib.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_multicast_service.h"
+#include "gnunet_psyc_service.h"
+
+/**
+ * Version number of GNUnet PSYCstore API.
+ */
+#define GNUNET_PSYCSTORE_VERSION 0x00000000
+
+/**
+ * Membership test failed.
+ */
+#define GNUNET_PSYCSTORE_MEMBERSHIP_TEST_FAILED -2
+
+/**
+ * Flags for stored messages.
+ */
+enum GNUNET_PSYCSTORE_MessageFlags
+{
+ /**
+ * The message contains state modifiers.
+ */
+ GNUNET_PSYCSTORE_MESSAGE_STATE = 1 << 0,
+
+ /**
+ * The state modifiers have been applied to the state store.
+ */
+ GNUNET_PSYCSTORE_MESSAGE_STATE_APPLIED = 1 << 1,
+
+ /**
+ * The message contains a state hash.
+ */
+ GNUNET_PSYCSTORE_MESSAGE_STATE_HASH = 1 << 2
+};
+
+
+/**
+ * Handle for a PSYCstore
+ */
+struct GNUNET_PSYCSTORE_Handle;
+
+
+/**
+ * Connect to the PSYCstore service.
+ *
+ * @param cfg Configuration to use.
+ *
+ * @return Handle for the connecton.
+ */
+struct GNUNET_PSYCSTORE_Handle *
+GNUNET_PSYCSTORE_connect (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Disconnect from the PSYCstore service.
+ *
+ * @param h Handle for the connection.
+ */
+void
+GNUNET_PSYCSTORE_disconnect (struct GNUNET_PSYCSTORE_Handle *h);
+
+
+/**
+ * Handle for an operation on the PSYCSTORE (useful to cancel the operation).
+ */
+struct GNUNET_PSYCSTORE_OperationHandle;
+
+
+/**
+ * Function called with the result of an asynchronous operation.
+ *
+ * @param cls
+ * Closure.
+ * @param result
+ * Result of the operation.
+ * @param err_msg
+ * Error message, or NULL if there's no error.
+ * @param err_msg_size
+ * Size of @a err_msg
+ */
+typedef void
+(*GNUNET_PSYCSTORE_ResultCallback) (void *cls,
+ int64_t result,
+ const char *err_msg,
+ uint16_t err_msg_size);
+
+
+/**
+ * Store join/leave events for a PSYC channel in order to be able to answer
+ * membership test queries later.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel where the event happened.
+ * @param slave_key
+ * Public key of joining/leaving slave.
+ * @param did_join
+ * #GNUNET_YES on join, #GNUNET_NO on part.
+ * @param announced_at
+ * ID of the message that announced the membership change.
+ * @param effective_since
+ * Message ID this membership change is in effect since.
+ * For joins it is <= announced_at, for parts it is always 0.
+ * @param group_generation
+ * In case of a part, the last group generation the slave has access to.
+ * It has relevance when a larger message have fragments with different
+ * group generations.
+ * @param result_cb
+ * Callback to call with the result of the storage operation.
+ * @param cls
+ * Closure for the callback.
+ *
+ * @return Operation handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_membership_store (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ int did_join,
+ uint64_t announced_at,
+ uint64_t effective_since,
+ uint64_t group_generation,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Test if a member was admitted to the channel at the given message ID.
+ *
+ * This is useful when relaying and replaying messages to check if a particular
+ * slave has access to the message fragment with a given group generation. It
+ * is also used when handling join requests to determine whether the slave is
+ * currently admitted to the channel.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param slave_key
+ * Public key of slave whose membership to check.
+ * @param message_id
+ * Message ID for which to do the membership test.
+ * @param group_generation
+ * Group generation of the fragment of the message to test.
+ * It has relevance if the message consists of multiple fragments with
+ * different group generations.
+ * @param result_cb
+ * Callback to call with the test result.
+ * @param cls
+ * Closure for the callback.
+ *
+ * @return Operation handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_membership_test (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t message_id,
+ uint64_t group_generation,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Store a message fragment sent to a channel.
+ *
+ * @param h Handle for the PSYCstore.
+ * @param channel_key The channel the message belongs to.
+ * @param msg Message to store.
+ * @param psycstore_flags Flags indicating whether the PSYC message contains
+ * state modifiers.
+ * @param result_cb Callback to call with the result of the operation.
+ * @param cls Closure for the callback.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_fragment_store (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_MULTICAST_MessageHeader *msg,
+ enum GNUNET_PSYCSTORE_MessageFlags psycstore_flags,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Function called with one message fragment, as the result of a
+ * GNUNET_PSYCSTORE_fragment_get() or GNUNET_PSYCSTORE_message_get() call.
+ *
+ * @param cls Closure.
+ * @param message The retrieved message fragment. A NULL value indicates that
+ * there are no more results to be returned.
+ * @param psycstore_flags Flags stored with the message.
+ *
+ * @return #GNUNET_NO to stop calling this callback with further fragments,
+ * #GNUNET_YES to continue.
+ */
+typedef int
+(*GNUNET_PSYCSTORE_FragmentCallback) (void *cls,
+ struct GNUNET_MULTICAST_MessageHeader *message,
+ enum GNUNET_PSYCSTORE_MessageFlags psycstore_flags);
+
+
+/**
+ * Retrieve message fragments by fragment ID range.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param slave_key
+ * The slave requesting the fragment. If not NULL, a membership test is
+ * performed first and the fragment is only returned if the slave has
+ * access to it.
+ * @param first_fragment_id
+ * First fragment ID to retrieve.
+ * Use 0 to get the latest message fragment.
+ * @param last_fragment_id
+ * Last consecutive fragment ID to retrieve.
+ * Use 0 to get the latest message fragment.
+ * @param fragment_cb
+ * Callback to call with the retrieved fragments.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_fragment_get (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t first_message_id,
+ uint64_t last_message_id,
+ GNUNET_PSYCSTORE_FragmentCallback fragment_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Retrieve latest message fragments.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param slave_key
+ * The slave requesting the fragment. If not NULL, a membership test is
+ * performed first and the fragment is only returned if the slave has
+ * access to it.
+ * @param first_fragment_id
+ * First fragment ID to retrieve.
+ * Use 0 to get the latest message fragment.
+ * @param last_fragment_id
+ * Last consecutive fragment ID to retrieve.
+ * Use 0 to get the latest message fragment.
+ * @param fragment_limit
+ * Maximum number of fragments to retrieve.
+ * @param fragment_cb
+ * Callback to call with the retrieved fragments.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_fragment_get_latest (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t fragment_limit,
+ GNUNET_PSYCSTORE_FragmentCallback fragment_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Retrieve all fragments of messages in a message ID range.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param slave_key
+ * The slave requesting the message.
+ * If not NULL, a membership test is performed first
+ * and the message is only returned if the slave has access to it.
+ * @param first_message_id
+ * First message ID to retrieve.
+ * @param last_message_id
+ * Last consecutive message ID to retrieve.
+ * @param fragment_limit
+ * Maximum number of fragments to retrieve.
+ * @param method_prefix
+ * Retrieve only messages with a matching method prefix.
+ * @param fragment_cb
+ * Callback to call with the retrieved fragments.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_message_get (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t first_message_id,
+ uint64_t last_message_id,
+ uint64_t fragment_limit,
+ const char *method_prefix,
+ GNUNET_PSYCSTORE_FragmentCallback fragment_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Retrieve all fragments of the latest messages.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param slave_key
+ * The slave requesting the message.
+ * If not NULL, a membership test is performed first
+ * and the message is only returned if the slave has access to it.
+ * @param message_limit
+ * Maximum number of messages to retrieve.
+ * @param method_prefix
+ * Retrieve only messages with a matching method prefix.
+ * @param fragment_cb
+ * Callback to call with the retrieved fragments.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_message_get_latest (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t message_limit,
+ const char *method_prefix,
+ GNUNET_PSYCSTORE_FragmentCallback fragment_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Retrieve a fragment of message specified by its message ID and fragment
+ * offset.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param slave_key
+ * The slave requesting the message fragment. If not NULL, a membership
+ * test is performed first and the message fragment is only returned
+ * if the slave has access to it.
+ * @param message_id
+ * Message ID to retrieve. Use 0 to get the latest message.
+ * @param fragment_offset
+ * Offset of the fragment to retrieve.
+ * @param fragment_cb
+ * Callback to call with the retrieved fragments.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_message_get_fragment (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ GNUNET_PSYCSTORE_FragmentCallback fragment_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Callback used to return the latest value of counters for the channel master.
+ *
+ * @see GNUNET_PSYCSTORE_counters_get()
+ *
+ * @param cls Closure.
+ * @param result_code
+ * Status code for the operation:
+ * #GNUNET_OK: success, counter values are returned.
+ * #GNUNET_NO: no message has been sent to the channel yet.
+ * #GNUNET_SYSERR: an error occurred.
+ * @param max_fragment_id
+ * Latest message fragment ID, used by multicast.
+ * @param max_message_id
+ * Latest message ID, used by PSYC.
+ * @param max_group_generation
+ * Latest group generation, used by PSYC.
+ * @param max_state_message_id
+ * Latest message ID containing state modifiers that
+ * was applied to the state store. Used for the state sync process.
+ */
+typedef void
+(*GNUNET_PSYCSTORE_CountersCallback) (void *cls,
+ int result_code,
+ uint64_t max_fragment_id,
+ uint64_t max_message_id,
+ uint64_t max_group_generation,
+ uint64_t max_state_message_id);
+
+
+/**
+ * Retrieve latest values of counters for a channel.
+ *
+ * The current value of counters are needed
+ * - when a channel master is restarted, so that it can continue incrementing
+ * the counters from their last value.
+ * - when a channel slave rejoins and starts the state synchronization process.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * Public key that identifies the channel.
+ * @param counters_cb
+ * Callback to call with the result.
+ * @param cls
+ * Closure for the @a ccb callback.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_counters_get (struct GNUNET_PSYCSTORE_Handle *h,
+ struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ GNUNET_PSYCSTORE_CountersCallback counters_cb,
+ void *cls);
+
+
+/**
+ * Apply modifiers of a message to the current channel state.
+ *
+ * An error is returned if there are missing messages containing state
+ * operations before the current one.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param message_id
+ * ID of the message that contains the @a modifiers.
+ * @param state_delta
+ * Value of the @e state_delta PSYC header variable of the message.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the @a result_cb callback.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_state_modify (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t message_id,
+ uint64_t state_delta,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Store synchronized state.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param max_state_message_id
+ * ID of the last stateful message before @a state_hash_message_id.
+ * @param state_hash_message_id
+ * ID of the message that contains the state_hash PSYC header variable.
+ * @param modifier_count
+ * Number of elements in the @a modifiers array.
+ * @param modifiers
+ * Full state to store.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callback.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_state_sync (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t max_state_message_id,
+ uint64_t state_hash_message_id,
+ size_t modifier_count,
+ const struct GNUNET_PSYC_Modifier *modifiers,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+
+/**
+ * Reset the state of a channel.
+ *
+ * Delete all state variables stored for the given channel.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callback.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_state_reset (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey
+ *channel_key,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Update signed values of state variables in the state store.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param message_id
+ * Message ID that contained the state @a hash.
+ * @param hash
+ * Hash of the serialized full state.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callback.
+ *
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_state_hash_update (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ uint64_t message_id,
+ const struct GNUNET_HashCode *hash,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Function called with the value of a state variable.
+ *
+ * @param cls
+ * Closure.
+ * @param name
+ * Name of the state variable. A NULL value indicates that there are no more
+ * state variables to be returned.
+ * @param value
+ * Value of the state variable.
+ * @param value_size
+ * Number of bytes in @a value.
+ *
+ * @return #GNUNET_NO to stop calling this callback with further variables,
+ * #GNUNET_YES to continue.
+ */;
+typedef int
+(*GNUNET_PSYCSTORE_StateCallback) (void *cls, const char *name,
+ const void *value, uint32_t value_size);
+
+
+/**
+ * Retrieve the best matching state variable.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param name
+ * Name of variable to match, the returned variable might be less specific.
+ * @param state_cb
+ * Callback to return the matching state variable.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_state_get (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const char *name,
+ GNUNET_PSYCSTORE_StateCallback state_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Retrieve all state variables for a channel with the given prefix.
+ *
+ * @param h
+ * Handle for the PSYCstore.
+ * @param channel_key
+ * The channel we are interested in.
+ * @param name_prefix
+ * Prefix of state variable names to match.
+ * @param state_cb
+ * Callback to return matching state variables.
+ * @param result_cb
+ * Callback to call with the result of the operation.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to cancel the operation.
+ */
+struct GNUNET_PSYCSTORE_OperationHandle *
+GNUNET_PSYCSTORE_state_get_prefix (struct GNUNET_PSYCSTORE_Handle *h,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key,
+ const char *name_prefix,
+ GNUNET_PSYCSTORE_StateCallback state_cb,
+ GNUNET_PSYCSTORE_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Cancel an operation.
+ *
+ * @param op Handle for the operation to cancel.
+ */
+int
+GNUNET_PSYCSTORE_operation_cancel (struct GNUNET_PSYCSTORE_OperationHandle *op);
+
+
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_PSYCSTORE_SERVICE_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/include/gnunet_social_service.h b/src/include/gnunet_social_service.h
new file mode 100644
index 0000000..7faa336
--- /dev/null
+++ b/src/include/gnunet_social_service.h
@@ -0,0 +1,1344 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2013 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 Gabor X Toth
+ * @author Christian Grothoff
+ *
+ * @file
+ * Social service; implements social interactions through the PSYC service.
+ */
+
+/** @defgroup social Social service
+Social interactions through the PSYC service.
+
+# Overview
+
+The social service provides an API for social interactions based on a one-to-many messaging model.
+It manages subscriptions of applications to places, provides messaging functionality in places,
+allows access to the local message history and manages the GNS zone of _egos_ (user identities).
+
+The service stores private and public keys of subscribed places, as well as files received in subscribed places.
+
+# Concepts and terminology
+
+## Ego, Nym
+
+An _ego_ is an identity of a user, a private-public key pair.
+A _nym_ is an identity of another user in the network, identified by its public key.
+Each user can have multiple identities.
+
+struct GNUNET_SOCIAL_Ego and struct GNUNET_SOCIAL_Nym represents one of these identities.
+
+## Place, Host, Guest
+
+A _place_ is where social interactions happen. It is owned and created by an _ego_.
+Creating a new place happens by an _ego_ entering a new place as a _host_,
+where _guests_ can enter later to receive messages sent to the place.
+
+A place is identified by its public key.
+
+- struct GNUNET_SOCIAL_Host represents a place entered as host,
+- struct GNUNET_SOCIAL_Guest is used for a place entered as guest.
+- A struct GNUNET_SOCIAL_Place can be obtained for both a host and guest place
+ using GNUNET_SOCIAL_host_get_place() and GNUNET_SOCIAL_guest_get_place()
+ and can be used with API functions common to hosts and guests.
+
+## History
+
+Messages sent to places are stored locally by the PSYCstore service, and can be queried any time.
+GNUNET_SOCIAL_history_replay_latest() retrieves the latest N messages sent to the place,
+while GNUNET_SOCIAL_history_replay() is used to query a given message ID range.
+
+## GNU Name System
+
+The GNU Name System is used for assigning human-readable names to nyms and places.
+There's a _GNS zone_ corresponding to each _nym_.
+An _ego_ can publish PKEY and PLACE records in its own zone, pointing to nyms and places, respectively.
+
+## Announcement, talk request
+
+The host can _announce_ messages to the place, using GNUNET_SOCIAL_host_announce().
+Guests can send _talk_ requests to the host, using GNUNET_SOCIAL_guest_talk().
+The host receives talk requests of guests and can _relay_ them to the place,
+or process it using a message handler function.
+
+# Using the API
+
+## Connecting to the service
+
+A client first establishes an _application connection_ to the service using
+GNUNET_SOCIAL_app_connect() providing its _application ID_, then receives the
+public keys of subscribed places and available egos in response.
+
+## Reconnecting to places
+
+Then the application can reconnect to its subscribed places by establishing
+_place connections_ with GNUNET_SOCIAL_host_enter_reconnect() and
+GNUNET_SOCIAL_guest_enter_reconnect().
+
+## Subscribing to a place
+
+Entering and subscribing a new host or guest place is done using
+GNUNET_SOCIAL_host_enter() and GNUNET_SOCIAL_guest_enter().
+
+## Disconnecting from a place
+
+An application can disconnect from a place while the social service keeps its
+network connection active, using GNUNET_SOCIAL_host_disconnect() and
+GNUNET_SOCIAL_guest_disconnect().
+
+## Leaving a place
+
+To permanently leave a place, see GNUNET_SOCIAL_host_leave() and GNUNET_SOCIAL_guest_leave().
+When leaving a place its network connections are closed and all applications are unsubscribed from the place.
+
+# Message methods
+
+## _converse
+
+Human conversation in a private or public place.
+
+### Environment
+
+#### _id_reply
+Message ID this message is in reply to.
+
+#### _id_thread
+Thread ID, the first message ID in the thread.
+
+#### _nym_author
+Nym of the author.
+
+FIXME: Are nyms a different data type from egos and person entities?
+Do they have a different format than any other entity address?
+Questions and thoughts on how to fix this in "questions.org"
+
+#### _sig_author
+Signature of the message body and its variables by the author.
+
+### Data
+
+Message body.
+
+## _notice_place
+
+Notification about a place.
+
+TODO: Applications can decide to auto-subscribe to certain places,
+e.g. files under a given size.
+
+### Environment
+
+#### Using GNS
+
+##### _gns_place
+GNS name of the place in a globally unique .zkey zone
+
+FIXME: A custom _gns PSYC data type should be avoidable by parsing
+and interpreting PSYC uniforms appropriately.
+Thoughts on this in "questions.org"
+
+#### Without GNS
+
+##### _key_pub_place
+Public key of place
+
+FIXME: _key_pub can't be the data type for GNUnet-specific cryptographic
+addressing. Questions and thoughts on how to fix this in "questions.org"
+
+##### _peer_origin
+Peer ID of origin
+
+##### _list_peer_relays
+List of peer IDs of relays
+
+## _notice_place_file
+
+Notification about a place hosting a file.
+
+### Environment
+
+The environment of _notice_place above, plus the following:
+
+#### _size_file
+Size of file
+
+#### _type_file
+MIME type of file
+
+#### _name_file
+Name of file
+
+#### _description_file
+Description of file
+
+## _file
+
+Messages with a _file method contain a file,
+which is saved to disk upon reception at the following location:
+$GNUNET_DATA_HOME/social/files/<H(place_pub)>/<H(message_id)>
+
+### Environment
+
+#### _size_file
+Size of file
+
+#### _type_file
+MIME type of file
+
+#### _name_file
+Name of file
+
+#### _description_file
+Description of file
+
+@{
+*/
+
+
+#ifndef GNUNET_SOCIAL_SERVICE_H
+#define GNUNET_SOCIAL_SERVICE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#if 0 /* keep Emacsens' auto-indent happy */
+}
+#endif
+#endif
+
+#include <stdint.h>
+#include "gnunet_util_lib.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_identity_service.h"
+#include "gnunet_namestore_service.h"
+#include "gnunet_psyc_service.h"
+
+
+/**
+ * Version number of GNUnet Social API.
+ */
+#define GNUNET_SOCIAL_VERSION 0x00000000
+
+/**
+ * Maximum size of client ID including '\0' terminator.
+ */
+#define GNUNET_SOCIAL_APP_MAX_ID_SIZE 256
+
+enum GNUNET_SOCIAL_MsgProcFlags {
+ GNUNET_SOCIAL_MSG_PROC_NONE = 0,
+ GNUNET_SOCIAL_MSG_PROC_RELAY = 1,
+ GNUNET_SOCIAL_MSG_PROC_SAVE= 2,
+};
+
+/**
+ * Handle for an application.
+ */
+struct GNUNET_SOCIAL_App;
+
+/**
+ * Handle for an ego (own identity)
+ */
+struct GNUNET_SOCIAL_Ego;
+
+/**
+ * Handle for a pseudonym of another user in the network.
+ */
+struct GNUNET_SOCIAL_Nym;
+
+/**
+ * Handle for a place where social interactions happen.
+ */
+struct GNUNET_SOCIAL_Place;
+
+/**
+ * Host handle for a place that we entered.
+ */
+struct GNUNET_SOCIAL_Host;
+
+/**
+ * Guest handle for place that we entered.
+ */
+struct GNUNET_SOCIAL_Guest;
+
+/**
+ * Handle that can be used to reconnect to a place as host.
+ */
+struct GNUNET_SOCIAL_HostConnection;
+
+/**
+ * Handle that can be used to reconnect to a place as guest.
+ */
+struct GNUNET_SOCIAL_GuestConnection;
+
+/**
+ * Notification about an available identity.
+ *
+ * @param cls
+ * Closure.
+ * @param pub_key
+ * Public key of ego.
+ * @param name
+ * Name of ego.
+ */
+typedef void
+(*GNUNET_SOCIAL_AppEgoCallback) (void *cls,
+ struct GNUNET_SOCIAL_Ego *ego,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *ego_pub_key,
+ const char *name);
+
+
+/**
+ * Entry status of a place per application.
+ */
+enum GNUNET_SOCIAL_AppPlaceState
+{
+ /**
+ * The place was once entered by the ego, but left since.
+ * It's possible to establish a local connection to the place
+ * without re-entering to fetch history from the PSYCstore.
+ * @see enum GNUNET_PSYC_SlaveJoinFlags and GNUNET_SOCIAL_guest_enter()
+ */
+ GNUNET_SOCIAL_PLACE_STATE_ARCHIVED = 0,
+
+ /**
+ * The place is entered by the ego,
+ * but this application is not subscribed to it.
+ */
+ GNUNET_SOCIAL_PLACE_STATE_ENTERED = 1,
+
+ /**
+ * The place is entered by the ego and
+ * and this application is subscribed to it.
+ */
+ GNUNET_SOCIAL_PLACE_STATE_SUBSCRIBED = 2,
+};
+
+
+/**
+ * Called after receiving initial list of egos and places.
+ */
+typedef void
+(*GNUNET_SOCIAL_AppConnectedCallback) (void *cls);
+
+
+/**
+ * Notification about a home.
+ *
+ * @param cls
+ * Closure.
+ * @param hconn
+ * Host connection, to be used with GNUNET_SOCIAL_host_enter_reconnect()
+ * @param ego
+ * Ego used to enter the place.
+ * @param place_pub_key
+ * Public key of the place.
+ * @param place_state
+ * @see enum GNUNET_SOCIAL_AppPlaceState
+ */
+typedef void
+(*GNUNET_SOCIAL_AppHostPlaceCallback) (void *cls,
+ struct GNUNET_SOCIAL_HostConnection *hconn,
+ struct GNUNET_SOCIAL_Ego *ego,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
+ enum GNUNET_SOCIAL_AppPlaceState place_state);
+
+/**
+ * Notification about a place.
+ *
+ * @param cls
+ * Closure.
+ * @param gconn
+ * Guest connection, to be used with GNUNET_SOCIAL_guest_enter_reconnect()
+ * @param ego
+ * Ego used to enter the place.
+ * @param place_pub_key
+ * Public key of the place.
+ * @param place_state
+ * @see enum GNUNET_SOCIAL_AppPlaceState
+ */
+typedef void
+(*GNUNET_SOCIAL_AppGuestPlaceCallback) (void *cls,
+ struct GNUNET_SOCIAL_GuestConnection *gconn,
+ struct GNUNET_SOCIAL_Ego *ego,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
+ enum GNUNET_SOCIAL_AppPlaceState place_state);
+
+
+/**
+ * Establish application connection to the social service.
+ *
+ * The @host_cb and @guest_cb functions are
+ * initially called for each entered places,
+ * then later each time a new place is entered with the current app ID.
+ *
+ * @param cfg
+ * Configuration.
+ * @param ego_cb
+ * Function to notify about an available ego.
+ * @param host_cb
+ * Function to notify about a place entered as host.
+ * @param guest_cb
+ * Function to notify about a place entered as guest.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle that can be used to stop listening.
+ */
+struct GNUNET_SOCIAL_App *
+GNUNET_SOCIAL_app_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *id,
+ GNUNET_SOCIAL_AppEgoCallback ego_cb,
+ GNUNET_SOCIAL_AppHostPlaceCallback host_cb,
+ GNUNET_SOCIAL_AppGuestPlaceCallback guest_cb,
+ GNUNET_SOCIAL_AppConnectedCallback connected_cb,
+ void *cls);
+
+
+/**
+ * Disconnect app.
+ *
+ * @param app
+ * Application handle.
+ * @param disconnect_cb
+ * Disconnect callback.
+ * @param disconnect_cls
+ * Disconnect closure.
+ */
+void
+GNUNET_SOCIAL_app_disconnect (struct GNUNET_SOCIAL_App *app,
+ GNUNET_ContinuationCallback disconnect_cb,
+ void *disconnect_cls);
+
+
+/**
+ * Get the public key of @a ego.
+ *
+ * @param ego
+ * Ego.
+ *
+ * @return Public key of ego.
+ */
+const struct GNUNET_CRYPTO_EcdsaPublicKey *
+GNUNET_SOCIAL_ego_get_pub_key (const struct GNUNET_SOCIAL_Ego *ego);
+
+
+/**
+ * Get the name of @a ego.
+ *
+ * @param ego
+ * Ego.
+ *
+ * @return Public key of @a ego.
+ */
+const char *
+GNUNET_SOCIAL_ego_get_name (const struct GNUNET_SOCIAL_Ego *ego);
+
+
+/**
+ * Get the public key of a @a nym.
+ *
+ * Suitable, for example, to be used with GNUNET_SOCIAL_zone_add_nym().
+ *
+ * @param nym
+ * Pseudonym to map to a cryptographic identifier.
+ *
+ * @return Public key of nym.
+ */
+const struct GNUNET_CRYPTO_EcdsaPublicKey *
+GNUNET_SOCIAL_nym_get_pub_key (const struct GNUNET_SOCIAL_Nym *nym);
+
+
+/**
+ * Get the hash of the public key of a @a nym.
+ *
+ * @param nym
+ * Pseudonym to map to a cryptographic identifier.
+ *
+ * @return Hash of the public key of nym.
+ */
+const struct GNUNET_HashCode *
+GNUNET_SOCIAL_nym_get_pub_key_hash (const struct GNUNET_SOCIAL_Nym *nym);
+
+
+/**
+ * Function called asking for nym to be admitted to the place.
+ *
+ * Should call either GNUNET_SOCIAL_host_admit() or
+ * GNUNET_SOCIAL_host_reject_entry() (possibly asynchronously). If this host
+ * cannot decide, it is fine to call neither function, in which case hopefully
+ * some other host of the place exists that will make the decision. The @a nym
+ * reference remains valid until the #GNUNET_SOCIAL_FarewellCallback is invoked
+ * for it.
+ *
+ * @param cls
+ * Closure.
+ * @param nym
+ * Handle for the user who wants to enter.
+ * @param method_name
+ * Method name in the entry request.
+ * @param variable_count
+ * Number of elements in the @a variables array.
+ * @param variables
+ * Variables present in the message.
+ * @param data
+ * Payload given on enter (e.g. a password).
+ * @param data_size
+ * Number of bytes in @a data.
+ */
+typedef void
+(*GNUNET_SOCIAL_AnswerDoorCallback) (void *cls,
+ struct GNUNET_SOCIAL_Nym *nym,
+ const char *method_name,
+ struct GNUNET_PSYC_Environment *env,
+ const void *data,
+ size_t data_size);
+
+
+/**
+ * Function called when a @a nym leaves the place.
+ *
+ * This is also called if the @a nym was never given permission to enter
+ * (i.e. the @a nym stopped asking to get in).
+ *
+ * @param cls
+ * Closure.
+ * @param nym
+ * Handle for the user who left.
+ */
+typedef void
+(*GNUNET_SOCIAL_FarewellCallback) (void *cls,
+ const struct GNUNET_SOCIAL_Nym *nym,
+ struct GNUNET_PSYC_Environment *env);
+
+
+/**
+ * Function called after the host entered a home.
+ *
+ * @param cls
+ * Closure.
+ * @param result
+ * #GNUNET_OK on success, or
+ * #GNUNET_SYSERR on error.
+ * @param place_pub_key
+ * Public key of home.
+ * @param max_message_id
+ * Last message ID sent to the channel.
+ * Or 0 if no messages have been sent to the place yet.
+ */
+typedef void
+(*GNUNET_SOCIAL_HostEnterCallback) (void *cls, int result,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
+ uint64_t max_message_id);
+
+
+/**
+ * Enter a place as host.
+ *
+ * A place is created upon first entering, and it is active until permanently
+ * left using GNUNET_SOCIAL_host_leave().
+ *
+ * @param cfg
+ * Configuration to contact the social service.
+ * @param ego
+ * Identity of the host.
+ * @param place_key
+ * Private-public key pair of the place.
+ * NULL for ephemeral places.
+ * @param policy
+ * Policy specifying entry and history restrictions for the place.
+ * @param slicer
+ * Slicer to handle incoming messages.
+ * @param enter_cb
+ * Function called when the place is entered and ready to use.
+ * @param answer_door_cb
+ * Function to handle new nyms that want to enter.
+ * @param farewell_cb
+ * Function to handle departing nyms.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle for the host.
+ */
+struct GNUNET_SOCIAL_Host *
+GNUNET_SOCIAL_host_enter (const struct GNUNET_SOCIAL_App *app,
+ const struct GNUNET_SOCIAL_Ego *ego,
+ enum GNUNET_PSYC_Policy policy,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_SOCIAL_HostEnterCallback enter_cb,
+ GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb,
+ GNUNET_SOCIAL_FarewellCallback farewell_cb,
+ void *cls);
+
+
+/**
+ * Reconnect to an already entered place as host.
+ *
+ * @param hconn
+ * Host connection handle.
+ * @see GNUNET_SOCIAL_app_connect() & GNUNET_SOCIAL_AppHostPlaceCallback()
+ * @param slicer
+ * Slicer to handle incoming messages.
+ * @param enter_cb
+ * Function called when the place is entered and ready to use.
+ * @param answer_door_cb
+ * Function to handle new nyms that want to enter.
+ * @param farewell_cb
+ * Function to handle departing nyms.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle for the host.
+ */
+struct GNUNET_SOCIAL_Host *
+GNUNET_SOCIAL_host_enter_reconnect (struct GNUNET_SOCIAL_HostConnection *hconn,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_SOCIAL_HostEnterCallback enter_cb,
+ GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb,
+ GNUNET_SOCIAL_FarewellCallback farewell_cb,
+ void *cls);
+
+
+/**
+ * Decision whether to admit @a nym into the place or refuse entry.
+ *
+ * @param hst
+ * Host of the place.
+ * @param nym
+ * Handle for the entity that wanted to enter.
+ * @param is_admitted
+ * #GNUNET_YES if @a nym is admitted,
+ * #GNUNET_NO if @a nym is refused entry,
+ * #GNUNET_SYSERR if we cannot answer the request.
+ * @param entry_resp
+ * Entry response message, or NULL.
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR if the message is too large.
+ */
+int
+GNUNET_SOCIAL_host_entry_decision (struct GNUNET_SOCIAL_Host *hst,
+ struct GNUNET_SOCIAL_Nym *nym,
+ int is_admitted,
+ const struct GNUNET_PSYC_Message *entry_resp);
+
+
+/**
+ * Throw @a nym out of the place.
+ *
+ * Sends a _notice_place_leave announcement to the home.
+ *
+ * The @a nym reference will remain valid until the
+ * #GNUNET_SOCIAL_FarewellCallback is invoked,
+ * which should be very soon after this call.
+ *
+ * @param host
+ * Host of the place.
+ * @param nym
+ * Handle for the entity to be ejected.
+ * @param env
+ * Environment for the message or NULL.
+ * _nym is set to @e nym regardless whether an @e env is provided.
+ */
+void
+GNUNET_SOCIAL_host_eject (struct GNUNET_SOCIAL_Host *host,
+ const struct GNUNET_SOCIAL_Nym *nym,
+ struct GNUNET_PSYC_Environment *env);
+
+
+/**
+ * Flags for announcements by a host.
+ */
+enum GNUNET_SOCIAL_AnnounceFlags
+{
+ GNUNET_SOCIAL_ANNOUNCE_NONE = 0,
+
+ /**
+ * Whether this announcement removes all objects from the place.
+ *
+ * New objects can be still added to the now empty place using the @e env
+ * parameter of the same announcement.
+ */
+ GNUNET_SOCIAL_ANNOUNCE_CLEAR_OBJECTS = 1 << 0
+};
+
+
+/**
+ * Handle for an announcement request.
+ */
+struct GNUNET_SOCIAL_Announcement;
+
+
+/**
+ * Send a message to all nyms that are present in the place.
+ *
+ * This function is restricted to the host. Nyms can only send requests
+ * to the host who can decide to relay it to everyone in the place.
+ *
+ * @param host
+ * Host of the place.
+ * @param method_name
+ * Method to use for the announcement.
+ * @param env
+ * Environment containing variables for the message and operations
+ * on objects of the place.
+ * Has to remain available until the first call to @a notify_data.
+ * Can be NULL.
+ * @param notify_data
+ * Function to call to get the payload of the announcement.
+ * @param notify_data_cls
+ * Closure for @a notify.
+ * @param flags
+ * Flags for this announcement.
+ *
+ * @return NULL on error (another announcement already in progress?).
+ */
+struct GNUNET_SOCIAL_Announcement *
+GNUNET_SOCIAL_host_announce (struct GNUNET_SOCIAL_Host *host,
+ const char *method_name,
+ const struct GNUNET_PSYC_Environment *env,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_data_cls,
+ enum GNUNET_SOCIAL_AnnounceFlags flags);
+
+
+/**
+ * Resume transmitting announcement.
+ *
+ * @param a
+ * The announcement to resume.
+ */
+void
+GNUNET_SOCIAL_host_announce_resume (struct GNUNET_SOCIAL_Announcement *a);
+
+
+/**
+ * Cancel announcement.
+ *
+ * @param a
+ * The announcement to cancel.
+ */
+void
+GNUNET_SOCIAL_host_announce_cancel (struct GNUNET_SOCIAL_Announcement *a);
+
+
+/**
+ * Allow relaying messages from guests matching a given @a method_prefix.
+ *
+ * @param host
+ * The host.
+ * @param method_prefix
+ * Method prefix to allow.
+ */
+void
+GNUNET_SOCIAL_host_relay_allow_method (struct GNUNET_SOCIAL_Host *host,
+ const char *method_prefix);
+
+
+/**
+ * Allow relaying changes to objects of the place.
+ *
+ * Only applies to messages with an allowed method name.
+ * @see GNUNET_SCOIAL_host_relay_allow_method()
+ *
+ * @param host
+ * The host.
+ * @param object_prefix
+ * Object prefix to allow modifying.
+ */
+void
+GNUNET_SOCIAL_host_relay_allow_method (struct GNUNET_SOCIAL_Host *host,
+ const char *object_prefix);
+
+
+/**
+ * Stop relaying messages from guests.
+ *
+ * Remove all allowed relay rules.
+ *
+ *
+ *
+ */
+void
+GNUNET_SOCIAL_host_relay_stop (struct GNUNET_SOCIAL_Host *host);
+
+
+/**
+ * Obtain handle for a hosted place.
+ *
+ * The returned handle can be used to access the place API.
+ *
+ * @param host
+ * Handle for the host.
+ *
+ * @return Handle for the hosted place, valid as long as @a host is valid.
+ */
+struct GNUNET_SOCIAL_Place *
+GNUNET_SOCIAL_host_get_place (struct GNUNET_SOCIAL_Host *host);
+
+
+/**
+ * Disconnect from a home.
+ *
+ * Invalidates host handle.
+ *
+ * @param hst
+ * The host to disconnect.
+ * @param disconnect_cb
+ * Function called after disconnected from the service.
+ * @param cls
+ * Closure for @a disconnect_cb.
+ */
+void
+GNUNET_SOCIAL_host_disconnect (struct GNUNET_SOCIAL_Host *hst,
+ GNUNET_ContinuationCallback disconnect_cb,
+ void *cls);
+
+
+/**
+ * Stop hosting a home.
+ *
+ * Sends a _notice_place_closing announcement to the home.
+ * Invalidates host handle.
+ *
+ * @param hst
+ * Host leaving.
+ * @param env
+ * Environment for the message or NULL.
+ * @param disconnect_cb
+ * Function called after the host left the place
+ * and disconnected from the service.
+ * @param cls
+ * Closure for @a disconnect_cb.
+ */
+void
+GNUNET_SOCIAL_host_leave (struct GNUNET_SOCIAL_Host *hst,
+ const struct GNUNET_PSYC_Environment *env,
+ GNUNET_ContinuationCallback disconnect_cb,
+ void *cls);
+
+
+/**
+ * Function called after the guest entered the local copy of the place.
+ *
+ * History and object query functions can be used after this call,
+ * but new messages can't be sent or received.
+ *
+ * @param cls
+ * Closure.
+ * @param result
+ * #GNUNET_OK on success, or
+ * #GNUNET_SYSERR on error, e.g. could not connect to the service, or
+ * could not resolve GNS name.
+ * @param place_pub_key
+ * Public key of place.
+ * @param max_message_id
+ * Last message ID sent to the place.
+ * Or 0 if no messages have been sent to the place yet.
+ */
+typedef void
+(*GNUNET_SOCIAL_GuestEnterCallback) (void *cls, int result,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
+ uint64_t max_message_id);
+
+
+/**
+ * Function called upon a guest receives a decision about entry to the place.
+ *
+ * @param is_admitted
+ * Is the guest admitted to the place?
+ * #GNUNET_YES if admitted,
+ * #GNUNET_NO if refused entry,
+ * #GNUNET_SYSERR if the request could not be answered.
+ * @param data
+ * Entry response message.
+ */
+typedef void
+(*GNUNET_SOCIAL_EntryDecisionCallback) (void *cls,
+ int is_admitted,
+ const struct GNUNET_PSYC_Message *entry_resp);
+
+
+/**
+ * Request entry to a place as a guest.
+ *
+ * @param app
+ * Application handle.
+ * @param ego
+ * Identity of the guest.
+ * @param place_pub_key
+ * Public key of the place to enter.
+ * @param flags
+ * Flags for the entry.
+ * @param origin
+ * Peer identity of the origin of the underlying multicast group.
+ * @param relay_count
+ * Number of elements in the @a relays array.
+ * @param relays
+ * Relays for the underlying multicast group.
+ * @param entry_msg
+ * Entry message.
+ * @param slicer
+ * Slicer to use for processing incoming requests from guests.
+ *
+ * @return NULL on errors, otherwise handle for the guest.
+ */
+struct GNUNET_SOCIAL_Guest *
+GNUNET_SOCIAL_guest_enter (const struct GNUNET_SOCIAL_App *app,
+ const struct GNUNET_SOCIAL_Ego *ego,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
+ enum GNUNET_PSYC_SlaveJoinFlags flags,
+ const struct GNUNET_PeerIdentity *origin,
+ uint32_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_PSYC_Message *entry_msg,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_SOCIAL_GuestEnterCallback local_enter_cb,
+ GNUNET_SOCIAL_EntryDecisionCallback entry_dcsn_cb,
+ void *cls);
+
+
+/**
+ * Request entry to a place by name as a guest.
+ *
+ * @param app
+ * Application handle.
+ * @param ego
+ * Identity of the guest.
+ * @param gns_name
+ * GNS name of the place to enter. Either in the form of
+ * 'room.friend.gnu', or 'NYMPUBKEY.zkey'. This latter case refers to
+ * the 'PLACE' record of the empty label ("+") in the GNS zone with the
+ * nym's public key 'NYMPUBKEY', and can be used to request entry to a
+ * pseudonym's place directly.
+ * @param password
+ * Password to decrypt the record, or NULL for cleartext records.
+ * @param join_msg
+ * Entry request message.
+ * @param slicer
+ * Slicer to use for processing incoming requests from guests.
+ * @param local_enter_cb
+ * Called upon connection established to the social service.
+ * @param entry_decision_cb
+ * Called upon receiving entry decision.
+ *
+ * @return NULL on errors, otherwise handle for the guest.
+ */
+struct GNUNET_SOCIAL_Guest *
+GNUNET_SOCIAL_guest_enter_by_name (const struct GNUNET_SOCIAL_App *app,
+ const struct GNUNET_SOCIAL_Ego *ego,
+ const char *gns_name,
+ const char *password,
+ const struct GNUNET_PSYC_Message *join_msg,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_SOCIAL_GuestEnterCallback local_enter_cb,
+ GNUNET_SOCIAL_EntryDecisionCallback entry_decision_cb,
+ void *cls);
+
+
+/**
+ * Reconnect to an already entered place as guest.
+ *
+ * @param gconn
+ * Guest connection handle.
+ * @see GNUNET_SOCIAL_app_connect() & GNUNET_SOCIAL_AppGuestPlaceCallback()
+ * @param flags
+ * Flags for the entry.
+ * @param slicer
+ * Slicer to use for processing incoming requests from guests.
+ * @param local_enter_cb
+ * Called upon connection established to the social service.
+ * @param entry_decision_cb
+ * Called upon receiving entry decision.
+ *
+ * @return NULL on errors, otherwise handle for the guest.
+ */
+struct GNUNET_SOCIAL_Guest *
+GNUNET_SOCIAL_guest_enter_reconnect (struct GNUNET_SOCIAL_GuestConnection *gconn,
+ enum GNUNET_PSYC_SlaveJoinFlags flags,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_SOCIAL_GuestEnterCallback local_enter_cb,
+ void *cls);
+
+
+/**
+ * Flags for talking to the host of a place.
+ */
+enum GNUNET_SOCIAL_TalkFlags
+{
+ GNUNET_SOCIAL_TALK_NONE = 0
+};
+
+
+/**
+ * A talk request.
+ */
+struct GNUNET_SOCIAL_TalkRequest;
+
+
+/**
+ * Talk to the host of the place.
+ *
+ * @param place
+ * Place where we want to talk to the host.
+ * @param method_name
+ * Method to invoke on the host.
+ * @param env
+ * Environment containing variables for the message, or NULL.
+ * @param notify_data
+ * Function to use to get the payload for the method.
+ * @param notify_data_cls
+ * Closure for @a notify_data.
+ * @param flags
+ * Flags for the message being sent.
+ *
+ * @return NULL if we are already trying to talk to the host,
+ * otherwise handle to cancel the request.
+ */
+struct GNUNET_SOCIAL_TalkRequest *
+GNUNET_SOCIAL_guest_talk (struct GNUNET_SOCIAL_Guest *guest,
+ const char *method_name,
+ const struct GNUNET_PSYC_Environment *env,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_data_cls,
+ enum GNUNET_SOCIAL_TalkFlags flags);
+
+
+/**
+ * Resume talking to the host of the place.
+ *
+ * @param tr
+ * Talk request to resume.
+ */
+void
+GNUNET_SOCIAL_guest_talk_resume (struct GNUNET_SOCIAL_TalkRequest *tr);
+
+
+/**
+ * Cancel talking to the host of the place.
+ *
+ * @param tr
+ * Talk request to cancel.
+ */
+void
+GNUNET_SOCIAL_guest_talk_cancel (struct GNUNET_SOCIAL_TalkRequest *tr);
+
+
+/**
+ * Disconnect from a place.
+ *
+ * Invalidates guest handle.
+ *
+ * @param gst
+ * The guest to disconnect.
+ * @param disconnect_cb
+ * Function called after disconnected from the service.
+ * @param cls
+ * Closure for @a disconnect_cb.
+ */
+void
+GNUNET_SOCIAL_guest_disconnect (struct GNUNET_SOCIAL_Guest *gst,
+ GNUNET_ContinuationCallback disconnect_cb,
+ void *cls);
+
+
+/**
+ * Leave a place temporarily or permanently.
+ *
+ * Notifies the owner of the place about leaving, and destroys the place handle.
+ *
+ * @param place
+ * Place to leave.
+ * @param env
+ * Optional environment for the leave message if @a keep_active
+ * is #GNUNET_NO. NULL if not needed.
+ * @param disconnect_cb
+ * Called upon disconnecting from the social service.
+ */
+void
+GNUNET_SOCIAL_guest_leave (struct GNUNET_SOCIAL_Guest *gst,
+ struct GNUNET_PSYC_Environment *env,
+ GNUNET_ContinuationCallback disconnect_cb,
+ void *leave_cls);
+
+
+/**
+ * Obtain handle for a place entered as guest.
+ *
+ * The returned handle can be used to access the place API.
+ *
+ * @param guest Handle for the guest.
+ *
+ * @return Handle for the place, valid as long as @a guest is valid.
+ */
+struct GNUNET_SOCIAL_Place *
+GNUNET_SOCIAL_guest_get_place (struct GNUNET_SOCIAL_Guest *guest);
+
+
+/**
+ * A history request.
+ */
+struct GNUNET_SOCIAL_HistoryRequest;
+
+
+/**
+ * Get the public key of a place.
+ *
+ * @param plc
+ * Place.
+ *
+ * @return Public key of the place.
+ */
+const struct GNUNET_CRYPTO_EddsaPublicKey *
+GNUNET_SOCIAL_place_get_pub_key (const struct GNUNET_SOCIAL_Place *plc);
+
+
+/**
+ * Set message processing @a flags for a @a method_prefix.
+ *
+ * @param plc
+ * Place.
+ * @param method_prefix
+ * Method prefix @a flags apply to.
+ * @param flags
+ * The flags that apply to a matching @a method_prefix.
+ */
+void
+GNUNET_SOCIAL_place_msg_proc_set (struct GNUNET_SOCIAL_Place *plc,
+ const char *method_prefix,
+ enum GNUNET_SOCIAL_MsgProcFlags flags);
+
+/**
+ * Clear all message processing flags previously set for this place.
+ */
+void
+GNUNET_SOCIAL_place_msg_proc_clear (struct GNUNET_SOCIAL_Place *plc);
+
+
+/**
+ * Learn about the history of a place.
+ *
+ * Messages are returned through the @a slicer function
+ * and have the #GNUNET_PSYC_MESSAGE_HISTORIC flag set.
+ *
+ * @param place
+ * Place we want to learn more about.
+ * @param start_message_id
+ * First historic message we are interested in.
+ * @param end_message_id
+ * Last historic message we are interested in (inclusive).
+ * @param method_prefix
+ * Only retrieve messages with this method prefix.
+ * @param flags
+ * OR'ed GNUNET_PSYC_HistoryReplayFlags
+ * @param slicer
+ * Slicer to use for retrieved messages.
+ * Can be the same as the slicer of the place.
+ * @param result_cb
+ * Function called after all messages retrieved.
+ * NULL if not needed.
+ * @param cls Closure for @a result_cb.
+ */
+struct GNUNET_SOCIAL_HistoryRequest *
+GNUNET_SOCIAL_place_history_replay (struct GNUNET_SOCIAL_Place *plc,
+ uint64_t start_message_id,
+ uint64_t end_message_id,
+ const char *method_prefix,
+ uint32_t flags,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Learn about the history of a place.
+ *
+ * Sends messages through the slicer function of the place where
+ * start_message_id <= message_id <= end_message_id.
+ * The messages will have the #GNUNET_PSYC_MESSAGE_HISTORIC flag set.
+ *
+ * To get the latest message, use 0 for both the start and end message ID.
+ *
+ * @param place
+ * Place we want to learn more about.
+ * @param message_limit
+ * Maximum number of historic messages we are interested in.
+ * @param result_cb
+ * Function called after all messages retrieved.
+ * NULL if not needed.
+ * @param cls Closure for @a result_cb.
+ */
+struct GNUNET_SOCIAL_HistoryRequest *
+GNUNET_SOCIAL_place_history_replay_latest (struct GNUNET_SOCIAL_Place *plc,
+ uint64_t message_limit,
+ const char *method_prefix,
+ uint32_t flags,
+ struct GNUNET_PSYC_Slicer *slicer,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+/**
+ * Cancel learning about the history of a place.
+ *
+ * @param hist
+ * History lesson to cancel.
+ */
+void
+GNUNET_SOCIAL_place_history_replay_cancel (struct GNUNET_SOCIAL_HistoryRequest *hist);
+
+
+struct GNUNET_SOCIAL_LookHandle;
+
+
+/**
+ * Look at a particular object in the place.
+ *
+ * The best matching object is returned (its name might be less specific than
+ * what was requested).
+ *
+ * @param place
+ * The place to look the object at.
+ * @param full_name
+ * Full name of the object.
+ *
+ * @return NULL if there is no such object at this place.
+ */
+struct GNUNET_SOCIAL_LookHandle *
+GNUNET_SOCIAL_place_look_at (struct GNUNET_SOCIAL_Place *plc,
+ const char *full_name,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+/**
+ * Look for objects in the place with a matching name prefix.
+ *
+ * @param place
+ * The place to look its objects at.
+ * @param name_prefix
+ * Look at objects with names beginning with this value.
+ * @param var_cb
+ * Function to call for each object found.
+ * @param cls
+ * Closure for callback function.
+ *
+ * @return Handle that can be used to stop looking at objects.
+ */
+struct GNUNET_SOCIAL_LookHandle *
+GNUNET_SOCIAL_place_look_for (struct GNUNET_SOCIAL_Place *plc,
+ const char *name_prefix,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls);
+
+
+/**
+ * Stop looking at objects.
+ *
+ * @param lh Look handle to stop.
+ */
+void
+GNUNET_SOCIAL_place_look_cancel (struct GNUNET_SOCIAL_LookHandle *lh);
+
+
+/**
+ * Advertise a @e place in the GNS zone of @a ego.
+ *
+ * @param app
+ * Application handle.
+ * @param ego
+ * Ego.
+ * @param place_pub_key
+ * Public key of place to add.
+ * @param name
+ * The name for the PLACE record to put in the zone.
+ * @param password
+ * Password used to encrypt the record or NULL to keep it cleartext.
+ * @param relay_count
+ * Number of elements in the @a relays array.
+ * @param relays
+ * List of relays to put in the PLACE record to advertise
+ * as entry points to the place in addition to the origin.
+ * @param expiration_time
+ * Expiration time of the record, use 0 to remove the record.
+ * @param result_cb
+ * Function called with the result of the operation.
+ * @param result_cls
+ * Closure for @a result_cb
+ *
+ * @return #GNUNET_OK if the request was sent,
+ * #GNUNET_SYSERR on error, e.g. the name/password is too long.
+ */
+int
+GNUNET_SOCIAL_zone_add_place (const struct GNUNET_SOCIAL_App *app,
+ const struct GNUNET_SOCIAL_Ego *ego,
+ const char *name,
+ const char *password,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
+ const struct GNUNET_PeerIdentity *origin,
+ uint32_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ struct GNUNET_TIME_Absolute expiration_time,
+ GNUNET_ResultCallback result_cb,
+ void *result_cls);
+
+
+/**
+ * Add public key to the GNS zone of the @e ego.
+ *
+ * @param cfg
+ * Configuration.
+ * @param ego
+ * Ego.
+ * @param name
+ * The name for the PKEY record to put in the zone.
+ * @param nym_pub_key
+ * Public key of nym to add.
+ * @param expiration_time
+ * Expiration time of the record, use 0 to remove the record.
+ * @param result_cb
+ * Function called with the result of the operation.
+ * @param result_cls
+ * Closure for @a result_cb
+ *
+ * @return #GNUNET_OK if the request was sent,
+ * #GNUNET_SYSERR on error, e.g. the name is too long.
+ */
+int
+GNUNET_SOCIAL_zone_add_nym (const struct GNUNET_SOCIAL_App *app,
+ const struct GNUNET_SOCIAL_Ego *ego,
+ const char *name,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *nym_pub_key,
+ struct GNUNET_TIME_Absolute expiration_time,
+ GNUNET_ResultCallback result_cb,
+ void *result_cls);
+
+
+#if 0 /* keep Emacsens' auto-indent happy */
+{
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef GNUNET_SOCIAL_SERVICE_H */
+#endif
+
+/** @} */ /* end of group */
diff --git a/src/multicast/.gitignore b/src/multicast/.gitignore
new file mode 100644
index 0000000..a97844e
--- /dev/null
+++ b/src/multicast/.gitignore
@@ -0,0 +1,7 @@
+gnunet-service-multicast
+gnunet-multicast
+test_multicast
+test_multicast_multipeer
+test_multicast_2peers
+test_multicast_multipeer_line
+test_multicast_multipeer_star
diff --git a/src/multicast/Makefile.am b/src/multicast/Makefile.am
new file mode 100644
index 0000000..61a9f8b
--- /dev/null
+++ b/src/multicast/Makefile.am
@@ -0,0 +1,79 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir= $(pkgdatadir)/config.d/
+
+libexecdir= $(pkglibdir)/libexec/
+
+pkgcfg_DATA = \
+ multicast.conf
+
+if MINGW
+ WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols
+endif
+
+if USE_COVERAGE
+ AM_CFLAGS = -fprofile-arcs -ftest-coverage
+endif
+
+lib_LTLIBRARIES = libgnunetmulticast.la
+
+libgnunetmulticast_la_SOURCES = \
+ multicast_api.c multicast.h
+libgnunetmulticast_la_LIBADD = \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(GN_LIBINTL) $(XLIB)
+libgnunetmulticast_la_LDFLAGS = \
+ $(GN_LIB_LDFLAGS) $(WINFLAGS) \
+ -version-info 0:0:0
+
+
+bin_PROGRAMS = \
+ gnunet-multicast
+
+libexec_PROGRAMS = \
+ gnunet-service-multicast \
+ $(EXP_LIBEXEC)
+
+gnunet_multicast_SOURCES = \
+ gnunet-multicast.c
+gnunet_multicast_LDADD = \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(GN_LIBINTL)
+
+gnunet_service_multicast_SOURCES = \
+ gnunet-service-multicast.c
+gnunet_service_multicast_LDADD = \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(top_builddir)/src/cadet/libgnunetcadet.la \
+ $(top_builddir)/src/statistics/libgnunetstatistics.la \
+ $(GN_LIBINTL)
+
+check_PROGRAMS = \
+ test_multicast \
+ test_multicast_multipeer_star \
+ test_multicast_multipeer_line
+
+if ENABLE_TEST_RUN
+AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@}; export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH; unset XDG_DATA_HOME; unset XDG_CONFIG_HOME;
+TESTS = $(check_PROGRAMS)
+endif
+
+test_multicast_SOURCES = \
+ test_multicast.c
+test_multicast_LDADD = \
+ libgnunetmulticast.la \
+ $(top_builddir)/src/testing/libgnunettesting.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+test_multicast_multipeer_star_SOURCES = \
+ test_multicast_multipeer.c
+test_multicast_multipeer_star_LDADD = \
+ libgnunetmulticast.la \
+ $(top_builddir)/src/testbed/libgnunettestbed.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+test_multicast_multipeer_line_SOURCES = \
+ test_multicast_multipeer.c
+test_multicast_multipeer_line_LDADD = \
+ libgnunetmulticast.la \
+ $(top_builddir)/src/testbed/libgnunettestbed.la \
+ $(top_builddir)/src/util/libgnunetutil.la
diff --git a/src/multicast/gnunet-multicast.c b/src/multicast/gnunet-multicast.c
new file mode 100644
index 0000000..63e1d52
--- /dev/null
+++ b/src/multicast/gnunet-multicast.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2013 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
+*/
+
+/**
+ * @file multicast/gnunet-multicast.c
+ * @brief multicast for writing a tool
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "gnunet_util_lib.h"
+/* #include "gnunet_multicast_service.h" */
+
+/**
+ * Final status code.
+ */
+static int ret;
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param cfg configuration
+ */
+static void
+run (void *cls, char *const *args, const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ /* main code here */
+ puts( gettext_noop ("This command doesn't do anything yet.") );
+ ret = -1;
+}
+
+
+/**
+ * The main function.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc, char *const *argv)
+{
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ /* FIMXE: add options here */
+ GNUNET_GETOPT_OPTION_END
+ };
+ if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
+ return 2;
+
+ ret = (GNUNET_OK ==
+ GNUNET_PROGRAM_run (argc, argv, "gnunet-multicast",
+ gettext_noop ("This command doesn't do anything yet."),
+ options, &run,
+ NULL)) ? ret : 1;
+ GNUNET_free ((void*) argv);
+ return ret;
+}
+
+/* end of gnunet-multicast.c */
diff --git a/src/multicast/gnunet-service-multicast.c b/src/multicast/gnunet-service-multicast.c
new file mode 100644
index 0000000..18c3661
--- /dev/null
+++ b/src/multicast/gnunet-service-multicast.c
@@ -0,0 +1,2234 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2009 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
+*/
+
+/**
+ * @file multicast/gnunet-service-multicast.c
+ * @brief program that does multicast
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_signatures.h"
+#include "gnunet_applications.h"
+#include "gnunet_statistics_service.h"
+#include "gnunet_cadet_service.h"
+#include "gnunet_multicast_service.h"
+#include "multicast.h"
+
+/**
+ * Handle to our current configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Service handle.
+ */
+static struct GNUNET_SERVICE_Handle *service;
+
+/**
+ * CADET handle.
+ */
+static struct GNUNET_CADET_Handle *cadet;
+
+/**
+ * Identity of this peer.
+ */
+static struct GNUNET_PeerIdentity this_peer;
+
+/**
+ * Handle to the statistics service.
+ */
+static struct GNUNET_STATISTICS_Handle *stats;
+
+/**
+ * All connected origin clients.
+ * Group's pub_key_hash -> struct Origin * (uniq)
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *origins;
+
+/**
+ * All connected member clients.
+ * Group's pub_key_hash -> struct Member * (multi)
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *members;
+
+/**
+ * Connected member clients per group.
+ * Group's pub_key_hash -> Member's pub_key_hash (uniq) -> struct Member * (uniq)
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *group_members;
+
+/**
+ * Incoming CADET channels with connected children in the tree.
+ * Group's pub_key_hash -> struct Channel * (multi)
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *channels_in;
+
+/**
+ * Outgoing CADET channels connecting to parents in the tree.
+ * Group's pub_key_hash -> struct Channel * (multi)
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *channels_out;
+
+/**
+ * Incoming replay requests from CADET.
+ * Group's pub_key_hash ->
+ * H(fragment_id, message_id, fragment_offset, flags) -> struct Channel *
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *replay_req_cadet;
+
+/**
+ * Incoming replay requests from clients.
+ * Group's pub_key_hash ->
+ * H(fragment_id, message_id, fragment_offset, flags) -> struct GNUNET_SERVICE_Client *
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *replay_req_client;
+
+
+/**
+ * Join status of a remote peer.
+ */
+enum JoinStatus
+{
+ JOIN_REFUSED = -1,
+ JOIN_NOT_ASKED = 0,
+ JOIN_WAITING = 1,
+ JOIN_ADMITTED = 2,
+};
+
+enum ChannelDirection
+{
+ DIR_INCOMING = 0,
+ DIR_OUTGOING = 1,
+};
+
+
+/**
+ * Context for a CADET channel.
+ */
+struct Channel
+{
+ /**
+ * Group the channel belongs to.
+ *
+ * Only set for outgoing channels.
+ */
+ struct Group *group;
+
+ /**
+ * CADET channel.
+ */
+ struct GNUNET_CADET_Channel *channel;
+
+ // FIXME: not used
+ /**
+ * CADET transmission handle.
+ */
+ struct GNUNET_CADET_TransmitHandle *tmit_handle;
+
+ /**
+ * Public key of the target group.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key;
+
+ /**
+ * Hash of @a group_pub_key.
+ */
+ struct GNUNET_HashCode group_pub_hash;
+
+ /**
+ * Public key of the joining member.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+ /**
+ * Remote peer identity.
+ */
+ struct GNUNET_PeerIdentity peer;
+
+ /**
+ * Current window size, set by cadet_notify_window_change()
+ */
+ int32_t window_size;
+
+ /**
+ * Is the connection established?
+ */
+ int8_t is_connected;
+
+ /**
+ * Is the remote peer admitted to the group?
+ * @see enum JoinStatus
+ */
+ int8_t join_status;
+
+ /**
+ * Number of messages waiting to be sent to CADET.
+ */
+ uint8_t msgs_pending;
+
+ /**
+ * Channel direction.
+ * @see enum ChannelDirection
+ */
+ uint8_t direction;
+};
+
+
+/**
+ * List of connected clients.
+ */
+struct ClientList
+{
+ struct ClientList *prev;
+ struct ClientList *next;
+ struct GNUNET_SERVICE_Client *client;
+};
+
+
+/**
+ * Client context for an origin or member.
+ */
+struct Group
+{
+ struct ClientList *clients_head;
+ struct ClientList *clients_tail;
+
+ /**
+ * Public key of the group.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey pub_key;
+
+ /**
+ * Hash of @a pub_key.
+ */
+ struct GNUNET_HashCode pub_key_hash;
+
+ /**
+ * CADET port hash.
+ */
+ struct GNUNET_HashCode cadet_port_hash;
+
+ /**
+ * Is the client disconnected? #GNUNET_YES or #GNUNET_NO
+ */
+ uint8_t is_disconnected;
+
+ /**
+ * Is this an origin (#GNUNET_YES), or member (#GNUNET_NO)?
+ */
+ uint8_t is_origin;
+
+ union {
+ struct Origin *origin;
+ struct Member *member;
+ };
+};
+
+
+/**
+* Client context for a group's origin.
+ */
+struct Origin
+{
+ struct Group group;
+
+ /**
+ * Private key of the group.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv_key;
+
+ /**
+ * CADET port.
+ */
+ struct GNUNET_CADET_Port *cadet_port;
+
+ /**
+ * Last message fragment ID sent to the group.
+ */
+ uint64_t max_fragment_id;
+};
+
+
+/**
+ * Client context for a group member.
+ */
+struct Member
+{
+ struct Group group;
+
+ /**
+ * Private key of the member.
+ */
+ struct GNUNET_CRYPTO_EcdsaPrivateKey priv_key;
+
+ /**
+ * Public key of the member.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey pub_key;
+
+ /**
+ * Hash of @a pub_key.
+ */
+ struct GNUNET_HashCode pub_key_hash;
+
+ /**
+ * Join request sent to the origin / members.
+ */
+ struct MulticastJoinRequestMessage *join_req;
+
+ /**
+ * Join decision sent in reply to our request.
+ *
+ * Only a positive decision is stored here, in case of a negative decision the
+ * client is disconnected.
+ */
+ struct MulticastJoinDecisionMessageHeader *join_dcsn;
+
+ /**
+ * CADET channel to the origin.
+ */
+ struct Channel *origin_channel;
+
+ /**
+ * Peer identity of origin.
+ */
+ struct GNUNET_PeerIdentity origin;
+
+ /**
+ * Peer identity of relays (other members to connect).
+ */
+ struct GNUNET_PeerIdentity *relays;
+
+ /**
+ * Last request fragment ID sent to the origin.
+ */
+ uint64_t max_fragment_id;
+
+ /**
+ * Number of @a relays.
+ */
+ uint32_t relay_count;
+};
+
+
+/**
+ * Client context.
+ */
+struct Client {
+ struct GNUNET_SERVICE_Client *client;
+ struct Group *group;
+};
+
+
+struct ReplayRequestKey
+{
+ uint64_t fragment_id;
+ uint64_t message_id;
+ uint64_t fragment_offset;
+ uint64_t flags;
+};
+
+
+static struct Channel *
+cadet_channel_create (struct Group *grp, struct GNUNET_PeerIdentity *peer);
+
+static void
+cadet_channel_destroy (struct Channel *chn);
+
+static void
+client_send_join_decision (struct Member *mem,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn);
+
+
+/**
+ * Task run during shutdown.
+ *
+ * @param cls unused
+ */
+static void
+shutdown_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "shutting down\n");
+ if (NULL != cadet)
+ {
+ GNUNET_CADET_disconnect (cadet);
+ cadet = NULL;
+ }
+ if (NULL != stats)
+ {
+ GNUNET_STATISTICS_destroy (stats, GNUNET_YES);
+ stats = NULL;
+ }
+ /* FIXME: do more clean up here */
+}
+
+
+/**
+ * Clean up origin data structures after a client disconnected.
+ */
+static void
+cleanup_origin (struct Origin *orig)
+{
+ struct Group *grp = &orig->group;
+ GNUNET_CONTAINER_multihashmap_remove (origins, &grp->pub_key_hash, orig);
+ if (NULL != orig->cadet_port)
+ {
+ GNUNET_CADET_close_port (orig->cadet_port);
+ orig->cadet_port = NULL;
+ }
+ GNUNET_free (orig);
+}
+
+
+/**
+ * Clean up member data structures after a client disconnected.
+ */
+static void
+cleanup_member (struct Member *mem)
+{
+ struct Group *grp = &mem->group;
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_mem = GNUNET_CONTAINER_multihashmap_get (group_members,
+ &grp->pub_key_hash);
+ GNUNET_assert (NULL != grp_mem);
+ GNUNET_CONTAINER_multihashmap_remove (grp_mem, &mem->pub_key_hash, mem);
+
+ if (0 == GNUNET_CONTAINER_multihashmap_size (grp_mem))
+ {
+ GNUNET_CONTAINER_multihashmap_remove (group_members, &grp->pub_key_hash,
+ grp_mem);
+ GNUNET_CONTAINER_multihashmap_destroy (grp_mem);
+ }
+ if (NULL != mem->join_dcsn)
+ {
+ GNUNET_free (mem->join_dcsn);
+ mem->join_dcsn = NULL;
+ }
+ if (NULL != mem->origin_channel)
+ {
+ GNUNET_CADET_channel_destroy (mem->origin_channel->channel);
+ mem->origin_channel = NULL;
+ }
+ GNUNET_CONTAINER_multihashmap_remove (members, &grp->pub_key_hash, mem);
+ GNUNET_free (mem);
+}
+
+
+/**
+ * Clean up group data structures after a client disconnected.
+ */
+static void
+cleanup_group (struct Group *grp)
+{
+ (GNUNET_YES == grp->is_origin)
+ ? cleanup_origin (grp->origin)
+ : cleanup_member (grp->member);
+}
+
+
+void
+replay_key_hash (uint64_t fragment_id, uint64_t message_id,
+ uint64_t fragment_offset, uint64_t flags,
+ struct GNUNET_HashCode *key_hash)
+{
+ struct ReplayRequestKey key = {
+ .fragment_id = fragment_id,
+ .message_id = message_id,
+ .fragment_offset = fragment_offset,
+ .flags = flags,
+ };
+ GNUNET_CRYPTO_hash (&key, sizeof (key), key_hash);
+}
+
+
+/**
+ * Remove channel from replay request hashmap.
+ *
+ * @param chn
+ * Channel to remove.
+ *
+ * @return #GNUNET_YES if there are more entries to process,
+ * #GNUNET_NO when reached end of hashmap.
+ */
+static int
+replay_req_remove_cadet (struct Channel *chn)
+{
+ if (NULL == chn || NULL == chn->group)
+ return GNUNET_SYSERR;
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet,
+ &chn->group->pub_key_hash);
+ if (NULL == grp_replay_req)
+ return GNUNET_NO;
+
+ struct GNUNET_CONTAINER_MultiHashMapIterator *
+ it = GNUNET_CONTAINER_multihashmap_iterator_create (grp_replay_req);
+ struct GNUNET_HashCode key;
+ const struct Channel *c;
+ while (GNUNET_YES
+ == GNUNET_CONTAINER_multihashmap_iterator_next (it, &key,
+ (const void **) &c))
+ {
+ if (c == chn)
+ {
+ GNUNET_CONTAINER_multihashmap_remove (grp_replay_req, &key, chn);
+ GNUNET_CONTAINER_multihashmap_iterator_destroy (it);
+ return GNUNET_YES;
+ }
+ }
+ GNUNET_CONTAINER_multihashmap_iterator_destroy (it);
+ return GNUNET_NO;
+}
+
+
+/**
+ * Remove client from replay request hashmap.
+ *
+ * @param client
+ * Client to remove.
+ *
+ * @return #GNUNET_YES if there are more entries to process,
+ * #GNUNET_NO when reached end of hashmap.
+ */
+static int
+replay_req_remove_client (struct Group *grp, struct GNUNET_SERVICE_Client *client)
+{
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_client,
+ &grp->pub_key_hash);
+ if (NULL == grp_replay_req)
+ return GNUNET_NO;
+
+ struct GNUNET_CONTAINER_MultiHashMapIterator *
+ it = GNUNET_CONTAINER_multihashmap_iterator_create (grp_replay_req);
+ struct GNUNET_HashCode key;
+ const struct GNUNET_SERVICE_Client *c;
+ while (GNUNET_YES
+ == GNUNET_CONTAINER_multihashmap_iterator_next (it, &key,
+ (const void **) &c))
+ {
+ if (c == client)
+ {
+ GNUNET_CONTAINER_multihashmap_remove (grp_replay_req, &key, client);
+ GNUNET_CONTAINER_multihashmap_iterator_destroy (it);
+ return GNUNET_YES;
+ }
+ }
+ GNUNET_CONTAINER_multihashmap_iterator_destroy (it);
+ return GNUNET_NO;
+}
+
+
+/**
+ * Send message to a client.
+ */
+static void
+client_send (struct GNUNET_SERVICE_Client *client,
+ const struct GNUNET_MessageHeader *msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "%p Sending message to client.\n", client);
+
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_copy (msg);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
+ env);
+}
+
+
+/**
+ * Send message to all clients connected to the group.
+ */
+static void
+client_send_group_keep_envelope (const struct Group *grp,
+ struct GNUNET_MQ_Envelope *env)
+{
+ struct ClientList *cli = grp->clients_head;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "%p Sending message to all clients of the group.\n",
+ grp);
+ while (NULL != cli)
+ {
+ GNUNET_MQ_send_copy (GNUNET_SERVICE_client_get_mq (cli->client),
+ env);
+ cli = cli->next;
+ }
+}
+
+
+/**
+ * Send message to all clients connected to the group and
+ * takes care of freeing @env.
+ */
+static void
+client_send_group (const struct Group *grp,
+ struct GNUNET_MQ_Envelope *env)
+{
+ client_send_group_keep_envelope (grp, env);
+ GNUNET_MQ_discard (env);
+}
+
+
+/**
+ * Iterator callback for sending a message to origin clients.
+ */
+static int
+client_send_origin_cb (void *cls, const struct GNUNET_HashCode *pub_key_hash,
+ void *origin)
+{
+ struct GNUNET_MQ_Envelope *env = cls;
+ struct Member *orig = origin;
+
+ client_send_group_keep_envelope (&orig->group, env);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Iterator callback for sending a message to member clients.
+ */
+static int
+client_send_member_cb (void *cls, const struct GNUNET_HashCode *pub_key_hash,
+ void *member)
+{
+ struct GNUNET_MQ_Envelope *env = cls;
+ struct Member *mem = member;
+
+ if (NULL != mem->join_dcsn)
+ { /* Only send message to admitted members */
+ client_send_group_keep_envelope (&mem->group, env);
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Send message to all origin and member clients connected to the group.
+ *
+ * @param pub_key_hash
+ * H(key_pub) of the group.
+ * @param msg
+ * Message to send.
+ */
+static int
+client_send_all (struct GNUNET_HashCode *pub_key_hash,
+ struct GNUNET_MQ_Envelope *env)
+{
+ int n = 0;
+ n += GNUNET_CONTAINER_multihashmap_get_multiple (origins, pub_key_hash,
+ client_send_origin_cb,
+ (void *) env);
+ n += GNUNET_CONTAINER_multihashmap_get_multiple (members, pub_key_hash,
+ client_send_member_cb,
+ (void *) env);
+ GNUNET_MQ_discard (env);
+ return n;
+}
+
+
+/**
+ * Send message to a random origin client or a random member client.
+ *
+ * @param grp The group to send @a msg to.
+ * @param msg Message to send.
+ */
+static int
+client_send_random (struct GNUNET_HashCode *pub_key_hash,
+ struct GNUNET_MQ_Envelope *env)
+{
+ int n = 0;
+ n = GNUNET_CONTAINER_multihashmap_get_random (origins, client_send_origin_cb,
+ (void *) env);
+ if (n <= 0)
+ n = GNUNET_CONTAINER_multihashmap_get_random (members, client_send_member_cb,
+ (void *) env);
+ GNUNET_MQ_discard (env);
+ return n;
+}
+
+
+/**
+ * Send message to all origin clients connected to the group.
+ *
+ * @param pub_key_hash
+ * H(key_pub) of the group.
+ * @param msg
+ * Message to send.
+ */
+static int
+client_send_origin (struct GNUNET_HashCode *pub_key_hash,
+ struct GNUNET_MQ_Envelope *env)
+{
+ int n = 0;
+ n += GNUNET_CONTAINER_multihashmap_get_multiple (origins, pub_key_hash,
+ client_send_origin_cb,
+ (void *) env);
+ return n;
+}
+
+
+/**
+ * Send fragment acknowledgement to all clients of the channel.
+ *
+ * @param pub_key_hash
+ * H(key_pub) of the group.
+ */
+static void
+client_send_ack (struct GNUNET_HashCode *pub_key_hash)
+{
+ struct GNUNET_MQ_Envelope *env;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending message ACK to client.\n");
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_FRAGMENT_ACK);
+ client_send_all (pub_key_hash, env);
+}
+
+
+struct CadetTransmitClosure
+{
+ struct Channel *chn;
+ const struct GNUNET_MessageHeader *msg;
+};
+
+
+/**
+ * Send a message to a CADET channel.
+ *
+ * @param chn Channel.
+ * @param msg Message.
+ */
+static void
+cadet_send_channel (struct Channel *chn, const struct GNUNET_MessageHeader *msg)
+{
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_copy (msg);
+
+ GNUNET_MQ_send (GNUNET_CADET_get_mq (chn->channel), env);
+
+ if (0 < chn->window_size)
+ {
+ client_send_ack (&chn->group_pub_hash);
+ }
+ else
+ {
+ chn->msgs_pending++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "%p Queuing message. Pending messages: %u\n",
+ chn, chn->msgs_pending);
+ }
+}
+
+
+/**
+ * Create CADET channel and send a join request.
+ */
+static void
+cadet_send_join_request (struct Member *mem)
+{
+ mem->origin_channel = cadet_channel_create (&mem->group, &mem->origin);
+ cadet_send_channel (mem->origin_channel, &mem->join_req->header);
+
+ uint32_t i;
+ for (i = 0; i < mem->relay_count; i++)
+ {
+ struct Channel *
+ chn = cadet_channel_create (&mem->group, &mem->relays[i]);
+ cadet_send_channel (chn, &mem->join_req->header);
+ }
+}
+
+
+static int
+cadet_send_join_decision_cb (void *cls,
+ const struct GNUNET_HashCode *group_pub_hash,
+ void *channel)
+{
+ const struct MulticastJoinDecisionMessageHeader *hdcsn = cls;
+ struct Channel *chn = channel;
+
+ const struct MulticastJoinDecisionMessage *dcsn =
+ (struct MulticastJoinDecisionMessage *) &hdcsn[1];
+
+ if (0 == memcmp (&hdcsn->member_pub_key, &chn->member_pub_key, sizeof (chn->member_pub_key))
+ && 0 == memcmp (&hdcsn->peer, &chn->peer, sizeof (chn->peer)))
+ {
+ if (GNUNET_YES == ntohl (dcsn->is_admitted))
+ {
+ chn->join_status = JOIN_ADMITTED;
+ }
+ else
+ {
+ chn->join_status = JOIN_REFUSED;
+ }
+ cadet_send_channel (chn, &hdcsn->header);
+ return GNUNET_YES;
+ }
+
+ // return GNUNET_YES to continue the multihashmap_get iteration
+ return GNUNET_YES;
+}
+
+
+/**
+ * Send join decision to a remote peer.
+ */
+static void
+cadet_send_join_decision (struct Group *grp,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ GNUNET_CONTAINER_multihashmap_get_multiple (channels_in, &grp->pub_key_hash,
+ &cadet_send_join_decision_cb,
+ (void *) hdcsn);
+}
+
+
+/**
+ * Iterator callback for sending a message to origin clients.
+ */
+static int
+cadet_send_cb (void *cls, const struct GNUNET_HashCode *pub_key_hash,
+ void *channel)
+{
+ const struct GNUNET_MessageHeader *msg = cls;
+ struct Channel *chn = channel;
+ if (JOIN_ADMITTED == chn->join_status)
+ cadet_send_channel (chn, msg);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Send message to all connected children.
+ */
+static int
+cadet_send_children (struct GNUNET_HashCode *pub_key_hash,
+ const struct GNUNET_MessageHeader *msg)
+{
+ int n = 0;
+ if (channels_in != NULL)
+ n += GNUNET_CONTAINER_multihashmap_get_multiple (channels_in, pub_key_hash,
+ cadet_send_cb, (void *) msg);
+ return n;
+}
+
+
+#if 0 // unused as yet
+/**
+ * Send message to all connected parents.
+ */
+static int
+cadet_send_parents (struct GNUNET_HashCode *pub_key_hash,
+ const struct GNUNET_MessageHeader *msg)
+{
+ int n = 0;
+ if (channels_in != NULL)
+ n += GNUNET_CONTAINER_multihashmap_get_multiple (channels_out, pub_key_hash,
+ cadet_send_cb, (void *) msg);
+ return n;
+}
+#endif
+
+
+/**
+ * CADET channel connect handler.
+ *
+ * @see GNUNET_CADET_ConnectEventHandler()
+ */
+static void *
+cadet_notify_connect (void *cls,
+ struct GNUNET_CADET_Channel *channel,
+ const struct GNUNET_PeerIdentity *source)
+{
+ struct Channel *chn = GNUNET_malloc (sizeof (struct Channel));
+ chn->group = cls;
+ chn->channel = channel;
+ chn->direction = DIR_INCOMING;
+ chn->join_status = JOIN_NOT_ASKED;
+
+ GNUNET_CONTAINER_multihashmap_put (channels_in, &chn->group->pub_key_hash, chn,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ return chn;
+}
+
+
+/**
+ * CADET window size change handler.
+ *
+ * @see GNUNET_CADET_WindowSizeEventHandler()
+ */
+static void
+cadet_notify_window_change (void *cls,
+ const struct GNUNET_CADET_Channel *channel,
+ int window_size)
+{
+ struct Channel *chn = cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "%p Window size changed to %d. Pending messages: %u\n",
+ chn, window_size, chn->msgs_pending);
+
+ chn->is_connected = GNUNET_YES;
+ chn->window_size = (int32_t) window_size;
+
+ for (int i = 0; i < window_size; i++)
+ {
+ if (0 < chn->msgs_pending)
+ {
+ client_send_ack (&chn->group_pub_hash);
+ chn->msgs_pending--;
+ }
+ else
+ {
+ break;
+ }
+ }
+}
+
+
+/**
+ * CADET channel disconnect handler.
+ *
+ * @see GNUNET_CADET_DisconnectEventHandler()
+ */
+static void
+cadet_notify_disconnect (void *cls,
+ const struct GNUNET_CADET_Channel *channel)
+{
+ if (NULL == cls)
+ return;
+
+ struct Channel *chn = cls;
+ if (NULL != chn->group)
+ {
+ if (GNUNET_NO == chn->group->is_origin)
+ {
+ struct Member *mem = (struct Member *) chn->group;
+ if (chn == mem->origin_channel)
+ mem->origin_channel = NULL;
+ }
+ }
+
+ int ret;
+ do
+ {
+ ret = replay_req_remove_cadet (chn);
+ }
+ while (GNUNET_YES == ret);
+
+ GNUNET_free (chn);
+}
+
+
+static int
+check_cadet_join_request (void *cls,
+ const struct MulticastJoinRequestMessage *req)
+{
+ struct Channel *chn = cls;
+
+ if (NULL == chn
+ || JOIN_NOT_ASKED != chn->join_status)
+ {
+ return GNUNET_SYSERR;
+ }
+
+ uint16_t size = ntohs (req->header.size);
+ if (size < sizeof (*req))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (ntohl (req->purpose.size) != (size
+ - sizeof (req->header)
+ - sizeof (req->reserved)
+ - sizeof (req->signature)))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST,
+ &req->purpose, &req->signature,
+ &req->member_pub_key))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming join request message from CADET.
+ */
+static void
+handle_cadet_join_request (void *cls,
+ const struct MulticastJoinRequestMessage *req)
+{
+ struct Channel *chn = cls;
+ GNUNET_CADET_receive_done (chn->channel);
+
+ struct GNUNET_HashCode group_pub_hash;
+ GNUNET_CRYPTO_hash (&req->group_pub_key, sizeof (req->group_pub_key), &group_pub_hash);
+ chn->group_pub_key = req->group_pub_key;
+ chn->group_pub_hash = group_pub_hash;
+ chn->member_pub_key = req->member_pub_key;
+ chn->peer = req->peer;
+ chn->join_status = JOIN_WAITING;
+
+ client_send_all (&group_pub_hash,
+ GNUNET_MQ_msg_copy (&req->header));
+}
+
+
+static int
+check_cadet_join_decision (void *cls,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ uint16_t size = ntohs (hdcsn->header.size);
+ if (size < sizeof (struct MulticastJoinDecisionMessageHeader) +
+ sizeof (struct MulticastJoinDecisionMessage))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ struct Channel *chn = cls;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == chn->group || GNUNET_NO != chn->group->is_origin)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ switch (chn->join_status)
+ {
+ case JOIN_REFUSED:
+ return GNUNET_SYSERR;
+
+ case JOIN_ADMITTED:
+ return GNUNET_OK;
+
+ case JOIN_NOT_ASKED:
+ case JOIN_WAITING:
+ break;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming join decision message from CADET.
+ */
+static void
+handle_cadet_join_decision (void *cls,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ const struct MulticastJoinDecisionMessage *
+ dcsn = (const struct MulticastJoinDecisionMessage *) &hdcsn[1];
+
+ struct Channel *chn = cls;
+ GNUNET_CADET_receive_done (chn->channel);
+
+ // FIXME: do we need to copy chn->peer or compare it with hdcsn->peer?
+ struct Member *mem = (struct Member *) chn->group;
+ client_send_join_decision (mem, hdcsn);
+ if (GNUNET_YES == ntohl (dcsn->is_admitted))
+ {
+ chn->join_status = JOIN_ADMITTED;
+ }
+ else
+ {
+ chn->join_status = JOIN_REFUSED;
+ cadet_channel_destroy (chn);
+ }
+}
+
+
+static int
+check_cadet_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ uint16_t size = ntohs (msg->header.size);
+ if (size < sizeof (*msg))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ struct Channel *chn = cls;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (ntohl (msg->purpose.size) != (size
+ - sizeof (msg->header)
+ - sizeof (msg->hop_counter)
+ - sizeof (msg->signature)))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (GNUNET_SIGNATURE_PURPOSE_MULTICAST_MESSAGE,
+ &msg->purpose, &msg->signature,
+ &chn->group_pub_key))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming multicast message from CADET.
+ */
+static void
+handle_cadet_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ struct Channel *chn = cls;
+ GNUNET_CADET_receive_done (chn->channel);
+ client_send_all (&chn->group_pub_hash,
+ GNUNET_MQ_msg_copy (&msg->header));
+}
+
+
+static int
+check_cadet_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ uint16_t size = ntohs (req->header.size);
+ if (size < sizeof (*req))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ struct Channel *chn = cls;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (ntohl (req->purpose.size) != (size
+ - sizeof (req->header)
+ - sizeof (req->member_pub_key)
+ - sizeof (req->signature)))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST,
+ &req->purpose, &req->signature,
+ &req->member_pub_key))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming multicast request message from CADET.
+ */
+static void
+handle_cadet_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ struct Channel *chn = cls;
+ GNUNET_CADET_receive_done (chn->channel);
+ client_send_origin (&chn->group_pub_hash,
+ GNUNET_MQ_msg_copy (&req->header));
+}
+
+
+// FIXME: do checks in handle_cadet_replay_request
+//static int
+//check_cadet_replay_request (void *cls,
+// const struct MulticastReplayRequestMessage *req)
+//{
+// uint16_t size = ntohs (req->header.size);
+// if (size < sizeof (*req))
+// {
+// GNUNET_break_op (0);
+// return GNUNET_SYSERR;
+// }
+//
+// struct Channel *chn = cls;
+// if (NULL == chn)
+// {
+// GNUNET_break_op (0);
+// return GNUNET_SYSERR;
+// }
+//
+// return GNUNET_OK;
+//}
+
+
+/**
+ * Incoming multicast replay request from CADET.
+ */
+static void
+handle_cadet_replay_request (void *cls,
+ const struct MulticastReplayRequestMessage *req)
+{
+ struct Channel *chn = cls;
+
+ GNUNET_CADET_receive_done (chn->channel);
+
+ struct MulticastReplayRequestMessage rep = *req;
+ GNUNET_memcpy (&rep.member_pub_key, &chn->member_pub_key, sizeof (chn->member_pub_key));
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet,
+ &chn->group->pub_key_hash);
+ if (NULL == grp_replay_req)
+ {
+ grp_replay_req = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ GNUNET_CONTAINER_multihashmap_put (replay_req_cadet,
+ &chn->group->pub_key_hash, grp_replay_req,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ }
+ struct GNUNET_HashCode key_hash;
+ replay_key_hash (rep.fragment_id,
+ rep.message_id,
+ rep.fragment_offset,
+ rep.flags,
+ &key_hash);
+ GNUNET_CONTAINER_multihashmap_put (grp_replay_req, &key_hash, chn,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+
+ client_send_random (&chn->group_pub_hash,
+ GNUNET_MQ_msg_copy (&rep.header));
+}
+
+
+static int
+check_cadet_replay_response (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ struct Channel *chn = cls;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming multicast replay response from CADET.
+ */
+static void
+handle_cadet_replay_response (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ struct Channel *chn = cls;
+ GNUNET_CADET_receive_done (chn->channel);
+
+ /* @todo FIXME: got replay error response, send request to other members */
+}
+
+
+static void
+group_set_cadet_port_hash (struct Group *grp)
+{
+ struct CadetPort {
+ struct GNUNET_CRYPTO_EddsaPublicKey pub_key;
+ uint32_t app_type;
+ } port = {
+ grp->pub_key,
+ GNUNET_APPLICATION_TYPE_MULTICAST,
+ };
+ GNUNET_CRYPTO_hash (&port, sizeof (port), &grp->cadet_port_hash);
+}
+
+
+
+/**
+ * Create new outgoing CADET channel.
+ *
+ * @param peer
+ * Peer to connect to.
+ * @param group_pub_key
+ * Public key of group the channel belongs to.
+ * @param group_pub_hash
+ * Hash of @a group_pub_key.
+ *
+ * @return Channel.
+ */
+static struct Channel *
+cadet_channel_create (struct Group *grp, struct GNUNET_PeerIdentity *peer)
+{
+ struct Channel *chn = GNUNET_malloc (sizeof (*chn));
+ chn->group = grp;
+ chn->group_pub_key = grp->pub_key;
+ chn->group_pub_hash = grp->pub_key_hash;
+ chn->peer = *peer;
+ chn->direction = DIR_OUTGOING;
+ chn->is_connected = GNUNET_NO;
+ chn->join_status = JOIN_WAITING;
+
+ struct GNUNET_MQ_MessageHandler cadet_handlers[] = {
+ GNUNET_MQ_hd_var_size (cadet_message,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE,
+ struct GNUNET_MULTICAST_MessageHeader,
+ chn),
+
+ GNUNET_MQ_hd_var_size (cadet_join_decision,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION,
+ struct MulticastJoinDecisionMessageHeader,
+ chn),
+
+ GNUNET_MQ_hd_fixed_size (cadet_replay_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST,
+ struct MulticastReplayRequestMessage,
+ chn),
+
+ GNUNET_MQ_hd_var_size (cadet_replay_response,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE,
+ struct MulticastReplayResponseMessage,
+ chn),
+
+ GNUNET_MQ_handler_end ()
+ };
+
+ chn->channel = GNUNET_CADET_channel_create (cadet, chn, &chn->peer,
+ &grp->cadet_port_hash,
+ GNUNET_CADET_OPTION_RELIABLE,
+ cadet_notify_window_change,
+ cadet_notify_disconnect,
+ cadet_handlers);
+ GNUNET_CONTAINER_multihashmap_put (channels_out, &chn->group_pub_hash, chn,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ return chn;
+}
+
+
+/**
+ * Destroy outgoing CADET channel.
+ */
+static void
+cadet_channel_destroy (struct Channel *chn)
+{
+ GNUNET_CADET_channel_destroy (chn->channel);
+ GNUNET_CONTAINER_multihashmap_remove_all (channels_out, &chn->group_pub_hash);
+ GNUNET_free (chn);
+}
+
+/**
+ * Handle a connecting client starting an origin.
+ */
+static void
+handle_client_origin_start (void *cls,
+ const struct MulticastOriginStartMessage *msg)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+
+ struct GNUNET_CRYPTO_EddsaPublicKey pub_key;
+ struct GNUNET_HashCode pub_key_hash;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&msg->group_key, &pub_key);
+ GNUNET_CRYPTO_hash (&pub_key, sizeof (pub_key), &pub_key_hash);
+
+ struct Origin *
+ orig = GNUNET_CONTAINER_multihashmap_get (origins, &pub_key_hash);
+ struct Group *grp;
+
+ if (NULL == orig)
+ {
+ orig = GNUNET_new (struct Origin);
+ orig->priv_key = msg->group_key;
+ orig->max_fragment_id = GNUNET_ntohll (msg->max_fragment_id);
+
+ grp = c->group = &orig->group;
+ grp->origin = orig;
+ grp->is_origin = GNUNET_YES;
+ grp->pub_key = pub_key;
+ grp->pub_key_hash = pub_key_hash;
+ grp->is_disconnected = GNUNET_NO;
+
+ GNUNET_CONTAINER_multihashmap_put (origins, &grp->pub_key_hash, orig,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+
+ group_set_cadet_port_hash (grp);
+
+ struct GNUNET_MQ_MessageHandler cadet_handlers[] = {
+ GNUNET_MQ_hd_var_size (cadet_message,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE,
+ struct GNUNET_MULTICAST_MessageHeader,
+ grp),
+
+ GNUNET_MQ_hd_var_size (cadet_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST,
+ struct GNUNET_MULTICAST_RequestHeader,
+ grp),
+
+ GNUNET_MQ_hd_var_size (cadet_join_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST,
+ struct MulticastJoinRequestMessage,
+ grp),
+
+ GNUNET_MQ_hd_fixed_size (cadet_replay_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST,
+ struct MulticastReplayRequestMessage,
+ grp),
+
+ GNUNET_MQ_hd_var_size (cadet_replay_response,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE,
+ struct MulticastReplayResponseMessage,
+ grp),
+
+ GNUNET_MQ_handler_end ()
+ };
+
+
+ orig->cadet_port = GNUNET_CADET_open_port (cadet,
+ &grp->cadet_port_hash,
+ cadet_notify_connect,
+ grp,
+ cadet_notify_window_change,
+ cadet_notify_disconnect,
+ cadet_handlers);
+ }
+ else
+ {
+ grp = &orig->group;
+ }
+
+ struct ClientList *cl = GNUNET_new (struct ClientList);
+ cl->client = client;
+ GNUNET_CONTAINER_DLL_insert (grp->clients_head, grp->clients_tail, cl);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Client connected as origin to group %s.\n",
+ orig, GNUNET_h2s (&grp->pub_key_hash));
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_member_join (void *cls,
+ const struct MulticastMemberJoinMessage *msg)
+{
+ uint16_t msg_size = ntohs (msg->header.size);
+ struct GNUNET_PeerIdentity *relays = (struct GNUNET_PeerIdentity *) &msg[1];
+ uint32_t relay_count = ntohl (msg->relay_count);
+
+ if (0 != relay_count)
+ {
+ if (UINT32_MAX / relay_count < sizeof (*relays)){
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "relay_count (%lu) * sizeof (*relays) (%lu) exceeds UINT32_MAX!\n",
+ (unsigned long)relay_count,
+ sizeof (*relays));
+ return GNUNET_SYSERR;
+ }
+ }
+ uint32_t relay_size = relay_count * sizeof (*relays);
+ struct GNUNET_MessageHeader *join_msg = NULL;
+ uint16_t join_msg_size = 0;
+ if (sizeof (*msg) + relay_size + sizeof (struct GNUNET_MessageHeader)
+ <= msg_size)
+ {
+ join_msg = (struct GNUNET_MessageHeader *)
+ (((char *) &msg[1]) + relay_size);
+ join_msg_size = ntohs (join_msg->size);
+ if (UINT16_MAX - join_msg_size < sizeof (struct MulticastJoinRequestMessage)){
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "join_msg_size (%u) + sizeof (struct MulticastJoinRequestMessage) (%lu) exceeds UINT16_MAX!\n",
+ (unsigned)join_msg_size,
+ (unsigned long)sizeof (struct MulticastJoinRequestMessage));
+ return GNUNET_SYSERR;
+ }
+ }
+ if (msg_size != (sizeof (*msg) + relay_size + join_msg_size)){
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "msg_size does not match real size of message!\n");
+ return GNUNET_SYSERR;
+ }else{
+ return GNUNET_OK;
+ }
+}
+
+
+/**
+ * Handle a connecting client joining a group.
+ */
+static void
+handle_client_member_join (void *cls,
+ const struct MulticastMemberJoinMessage *msg)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+
+ uint16_t msg_size = ntohs (msg->header.size);
+
+ struct GNUNET_CRYPTO_EcdsaPublicKey mem_pub_key;
+ struct GNUNET_HashCode pub_key_hash, mem_pub_key_hash;
+
+ GNUNET_CRYPTO_ecdsa_key_get_public (&msg->member_key, &mem_pub_key);
+ GNUNET_CRYPTO_hash (&mem_pub_key, sizeof (mem_pub_key), &mem_pub_key_hash);
+ GNUNET_CRYPTO_hash (&msg->group_pub_key, sizeof (msg->group_pub_key), &pub_key_hash);
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_mem = GNUNET_CONTAINER_multihashmap_get (group_members, &pub_key_hash);
+ struct Member *mem = NULL;
+ struct Group *grp;
+
+ if (NULL != grp_mem)
+ {
+ mem = GNUNET_CONTAINER_multihashmap_get (grp_mem, &mem_pub_key_hash);
+ }
+
+ if (NULL == mem)
+ {
+ mem = GNUNET_new (struct Member);
+ mem->origin = msg->origin;
+ mem->priv_key = msg->member_key;
+ mem->pub_key = mem_pub_key;
+ mem->pub_key_hash = mem_pub_key_hash;
+ mem->max_fragment_id = 0; // FIXME
+
+ grp = c->group = &mem->group;
+ grp->member = mem;
+ grp->is_origin = GNUNET_NO;
+ grp->pub_key = msg->group_pub_key;
+ grp->pub_key_hash = pub_key_hash;
+ grp->is_disconnected = GNUNET_NO;
+ group_set_cadet_port_hash (grp);
+
+ if (NULL == grp_mem)
+ {
+ grp_mem = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ GNUNET_CONTAINER_multihashmap_put (group_members, &grp->pub_key_hash, grp_mem,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ }
+ GNUNET_CONTAINER_multihashmap_put (grp_mem, &mem->pub_key_hash, mem,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+
+ // FIXME: should the members hash map have option UNIQUE_FAST?
+ GNUNET_CONTAINER_multihashmap_put (members, &grp->pub_key_hash, mem,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ }
+ else
+ {
+ grp = &mem->group;
+ }
+
+ struct ClientList *cl = GNUNET_new (struct ClientList);
+ cl->client = client;
+ GNUNET_CONTAINER_DLL_insert (grp->clients_head, grp->clients_tail, cl);
+
+ char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&mem->pub_key);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Client connected to group %s as member %s (%s). size = %d\n",
+ GNUNET_h2s (&grp->pub_key_hash),
+ GNUNET_h2s2 (&mem->pub_key_hash),
+ str,
+ GNUNET_CONTAINER_multihashmap_size (members));
+ GNUNET_free (str);
+
+ if (NULL != mem->join_dcsn)
+ { /* Already got a join decision, send it to client. */
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_copy (&mem->join_dcsn->header);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
+ env);
+ }
+ else
+ { /* First client of the group, send join request. */
+ struct GNUNET_PeerIdentity *relays = (struct GNUNET_PeerIdentity *) &msg[1];
+ uint32_t relay_count = ntohl (msg->relay_count);
+ uint16_t relay_size = relay_count * sizeof (*relays);
+ struct GNUNET_MessageHeader *join_msg = NULL;
+ uint16_t join_msg_size = 0;
+ if (sizeof (*msg) + relay_size + sizeof (struct GNUNET_MessageHeader)
+ <= msg_size)
+ {
+ join_msg = (struct GNUNET_MessageHeader *)
+ (((char *) &msg[1]) + relay_size);
+ join_msg_size = ntohs (join_msg->size);
+ }
+
+ uint16_t req_msg_size = sizeof (struct MulticastJoinRequestMessage) + join_msg_size;
+ struct MulticastJoinRequestMessage *
+ req = GNUNET_malloc (req_msg_size);
+ req->header.size = htons (req_msg_size);
+ req->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST);
+ req->group_pub_key = grp->pub_key;
+ req->peer = this_peer;
+ GNUNET_CRYPTO_ecdsa_key_get_public (&mem->priv_key, &req->member_pub_key);
+ if (0 < join_msg_size)
+ GNUNET_memcpy (&req[1], join_msg, join_msg_size);
+
+ req->member_pub_key = mem->pub_key;
+ req->purpose.size = htonl (req_msg_size
+ - sizeof (req->header)
+ - sizeof (req->reserved)
+ - sizeof (req->signature));
+ req->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST);
+
+ if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_sign (&mem->priv_key, &req->purpose,
+ &req->signature))
+ {
+ /* FIXME: handle error */
+ GNUNET_assert (0);
+ }
+
+ if (NULL != mem->join_req)
+ GNUNET_free (mem->join_req);
+ mem->join_req = req;
+
+ if (0 ==
+ client_send_origin (&grp->pub_key_hash,
+ GNUNET_MQ_msg_copy (&mem->join_req->header)))
+ { /* No local origins, send to remote origin */
+ cadet_send_join_request (mem);
+ }
+ }
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static void
+client_send_join_decision (struct Member *mem,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ client_send_group (&mem->group, GNUNET_MQ_msg_copy (&hdcsn->header));
+
+ const struct MulticastJoinDecisionMessage *
+ dcsn = (const struct MulticastJoinDecisionMessage *) &hdcsn[1];
+ if (GNUNET_YES == ntohl (dcsn->is_admitted))
+ { /* Member admitted, store join_decision. */
+ uint16_t dcsn_size = ntohs (dcsn->header.size);
+ mem->join_dcsn = GNUNET_malloc (dcsn_size);
+ GNUNET_memcpy (mem->join_dcsn, dcsn, dcsn_size);
+ }
+ else
+ { /* Refused entry, but replay would be still possible for past members. */
+ }
+}
+
+
+static int
+check_client_join_decision (void *cls,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Join decision from client.
+ */
+static void
+handle_client_join_decision (void *cls,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_NO == grp->is_disconnected);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p got join decision from client for group %s..\n",
+ grp, GNUNET_h2s (&grp->pub_key_hash));
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_mem = GNUNET_CONTAINER_multihashmap_get (group_members,
+ &grp->pub_key_hash);
+ struct Member *mem = NULL;
+ if (NULL != grp_mem)
+ {
+ struct GNUNET_HashCode member_key_hash;
+ GNUNET_CRYPTO_hash (&hdcsn->member_pub_key, sizeof (hdcsn->member_pub_key),
+ &member_key_hash);
+ mem = GNUNET_CONTAINER_multihashmap_get (grp_mem, &member_key_hash);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p ..and member %s: %p\n",
+ grp, GNUNET_h2s (&member_key_hash), mem);
+ }
+
+ if (NULL != mem)
+ { /* Found local member */
+ client_send_join_decision (mem, hdcsn);
+ }
+ else
+ { /* Look for remote member */
+ cadet_send_join_decision (grp, hdcsn);
+ }
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static void
+handle_client_part_request (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+ struct GNUNET_MQ_Envelope *env;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_NO == grp->is_disconnected);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p got part request from client for group %s.\n",
+ grp, GNUNET_h2s (&grp->pub_key_hash));
+ grp->is_disconnected = GNUNET_YES;
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_PART_ACK);
+ client_send_group (grp, env);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_multicast_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming message from a client.
+ */
+static void
+handle_client_multicast_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ // FIXME: what if GNUNET_YES == grp->is_disconnected? Do we allow sending messages?
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_YES == grp->is_origin);
+ struct Origin *orig = grp->origin;
+
+ // FIXME: use GNUNET_MQ_msg_copy
+ /* FIXME: yucky, should use separate message structs for P2P and CS! */
+ struct GNUNET_MULTICAST_MessageHeader *
+ out = (struct GNUNET_MULTICAST_MessageHeader *) GNUNET_copy_message (&msg->header);
+ out->fragment_id = GNUNET_htonll (++orig->max_fragment_id);
+ out->purpose.size = htonl (ntohs (out->header.size)
+ - sizeof (out->header)
+ - sizeof (out->hop_counter)
+ - sizeof (out->signature));
+ out->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_MULTICAST_MESSAGE);
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (&orig->priv_key, &out->purpose,
+ &out->signature))
+ {
+ GNUNET_assert (0);
+ }
+
+ client_send_all (&grp->pub_key_hash, GNUNET_MQ_msg_copy (&out->header));
+ cadet_send_children (&grp->pub_key_hash, &out->header);
+ client_send_ack (&grp->pub_key_hash);
+ GNUNET_free (out);
+
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_multicast_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming request from a client.
+ */
+static void
+handle_client_multicast_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_NO == grp->is_disconnected);
+ GNUNET_assert (GNUNET_NO == grp->is_origin);
+ struct Member *mem = grp->member;
+
+ /* FIXME: yucky, should use separate message structs for P2P and CS! */
+ struct GNUNET_MULTICAST_RequestHeader *
+ out = (struct GNUNET_MULTICAST_RequestHeader *) GNUNET_copy_message (&req->header);
+ out->member_pub_key = mem->pub_key;
+ out->fragment_id = GNUNET_ntohll (++mem->max_fragment_id);
+ out->purpose.size = htonl (ntohs (out->header.size)
+ - sizeof (out->header)
+ - sizeof (out->member_pub_key)
+ - sizeof (out->signature));
+ out->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST);
+
+ if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_sign (&mem->priv_key, &out->purpose,
+ &out->signature))
+ {
+ GNUNET_assert (0);
+ }
+
+ uint8_t send_ack = GNUNET_YES;
+ if (0 ==
+ client_send_origin (&grp->pub_key_hash,
+ GNUNET_MQ_msg_copy (&out->header)))
+ { /* No local origins, send to remote origin */
+ if (NULL != mem->origin_channel)
+ {
+ cadet_send_channel (mem->origin_channel, &out->header);
+ send_ack = GNUNET_NO;
+ }
+ else
+ {
+ /* FIXME: not yet connected to origin */
+ GNUNET_SERVICE_client_drop (client);
+ GNUNET_free (out);
+ return;
+ }
+ }
+ if (GNUNET_YES == send_ack)
+ {
+ client_send_ack (&grp->pub_key_hash);
+ }
+ GNUNET_free (out);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+/**
+ * Incoming replay request from a client.
+ */
+static void
+handle_client_replay_request (void *cls,
+ const struct MulticastReplayRequestMessage *rep)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_NO == grp->is_disconnected);
+ GNUNET_assert (GNUNET_NO == grp->is_origin);
+ struct Member *mem = grp->member;
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_client,
+ &grp->pub_key_hash);
+ if (NULL == grp_replay_req)
+ {
+ grp_replay_req = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ GNUNET_CONTAINER_multihashmap_put (replay_req_client,
+ &grp->pub_key_hash, grp_replay_req,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ }
+
+ struct GNUNET_HashCode key_hash;
+ replay_key_hash (rep->fragment_id, rep->message_id, rep->fragment_offset,
+ rep->flags, &key_hash);
+ GNUNET_CONTAINER_multihashmap_put (grp_replay_req, &key_hash, client,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+
+ if (0 ==
+ client_send_origin (&grp->pub_key_hash,
+ GNUNET_MQ_msg_copy (&rep->header)))
+ { /* No local origin, replay from remote members / origin. */
+ if (NULL != mem->origin_channel)
+ {
+ cadet_send_channel (mem->origin_channel, &rep->header);
+ }
+ else
+ {
+ /* FIXME: not yet connected to origin */
+
+ GNUNET_assert (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ }
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+cadet_send_replay_response_cb (void *cls,
+ const struct GNUNET_HashCode *key_hash,
+ void *value)
+{
+ struct Channel *chn = value;
+ struct GNUNET_MessageHeader *msg = cls;
+
+ cadet_send_channel (chn, msg);
+ return GNUNET_OK;
+}
+
+
+static int
+client_send_replay_response_cb (void *cls,
+ const struct GNUNET_HashCode *key_hash,
+ void *value)
+{
+ struct GNUNET_SERVICE_Client *client = value;
+ struct GNUNET_MessageHeader *msg = cls;
+
+ client_send (client, msg);
+ return GNUNET_OK;
+}
+
+
+static int
+check_client_replay_response_end (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * End of replay response from a client.
+ */
+static void
+handle_client_replay_response_end (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_NO == grp->is_disconnected);
+
+ struct GNUNET_HashCode key_hash;
+ replay_key_hash (res->fragment_id, res->message_id, res->fragment_offset,
+ res->flags, &key_hash);
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req_cadet = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet,
+ &grp->pub_key_hash);
+ if (NULL != grp_replay_req_cadet)
+ {
+ GNUNET_CONTAINER_multihashmap_remove_all (grp_replay_req_cadet, &key_hash);
+ }
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req_client = GNUNET_CONTAINER_multihashmap_get (replay_req_client,
+ &grp->pub_key_hash);
+ if (NULL != grp_replay_req_client)
+ {
+ GNUNET_CONTAINER_multihashmap_remove_all (grp_replay_req_client, &key_hash);
+ }
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_replay_response (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ const struct GNUNET_MessageHeader *msg;
+ if (GNUNET_MULTICAST_REC_OK == res->error_code)
+ {
+ msg = GNUNET_MQ_extract_nested_mh (res);
+ if (NULL == msg)
+ {
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming replay response from a client.
+ *
+ * Respond with a multicast message on success, or otherwise with an error code.
+ */
+static void
+handle_client_replay_response (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Group *grp = c->group;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_NO == grp->is_disconnected);
+
+ const struct GNUNET_MessageHeader *msg = &res->header;
+ if (GNUNET_MULTICAST_REC_OK == res->error_code)
+ {
+ msg = GNUNET_MQ_extract_nested_mh (res);
+ }
+
+ struct GNUNET_HashCode key_hash;
+ replay_key_hash (res->fragment_id, res->message_id, res->fragment_offset,
+ res->flags, &key_hash);
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req_cadet = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet,
+ &grp->pub_key_hash);
+ if (NULL != grp_replay_req_cadet)
+ {
+ GNUNET_CONTAINER_multihashmap_get_multiple (grp_replay_req_cadet, &key_hash,
+ cadet_send_replay_response_cb,
+ (void *) msg);
+ }
+ if (GNUNET_MULTICAST_REC_OK == res->error_code)
+ {
+ struct GNUNET_CONTAINER_MultiHashMap *
+ grp_replay_req_client = GNUNET_CONTAINER_multihashmap_get (replay_req_client,
+ &grp->pub_key_hash);
+ if (NULL != grp_replay_req_client)
+ {
+ GNUNET_CONTAINER_multihashmap_get_multiple (grp_replay_req_client, &key_hash,
+ client_send_replay_response_cb,
+ (void *) msg);
+ }
+ }
+ else
+ {
+ handle_client_replay_response_end (c, res);
+ return;
+ }
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+/**
+ * A new client connected.
+ *
+ * @param cls NULL
+ * @param client client to add
+ * @param mq message queue for @a client
+ * @return @a client
+ */
+static void *
+client_notify_connect (void *cls,
+ struct GNUNET_SERVICE_Client *client,
+ struct GNUNET_MQ_Handle *mq)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client connected: %p\n", client);
+ /* FIXME: send connect ACK */
+
+ struct Client *c = GNUNET_new (struct Client);
+ c->client = client;
+
+ return c;
+}
+
+
+/**
+ * Called whenever a client is disconnected.
+ * Frees our resources associated with that client.
+ *
+ * @param cls closure
+ * @param client identification of the client
+ * @param app_ctx must match @a client
+ */
+static void
+client_notify_disconnect (void *cls,
+ struct GNUNET_SERVICE_Client *client,
+ void *app_ctx)
+{
+ struct Client *c = app_ctx;
+ struct Group *grp = c->group;
+ GNUNET_free (c);
+
+ if (NULL == grp)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p User context is NULL in client_disconnect()\n", grp);
+ GNUNET_break (0);
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Client (%s) disconnected from group %s\n",
+ grp, (GNUNET_YES == grp->is_origin) ? "origin" : "member",
+ GNUNET_h2s (&grp->pub_key_hash));
+
+ // FIXME (due to protocol change): here we must not remove all clients,
+ // only the one we were notified about!
+ struct ClientList *cl = grp->clients_head;
+ while (NULL != cl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "iterating clients for group %p\n",
+ grp);
+ if (cl->client == client)
+ {
+ GNUNET_CONTAINER_DLL_remove (grp->clients_head, grp->clients_tail, cl);
+ GNUNET_free (cl);
+ break;
+ }
+ cl = cl->next;
+ }
+
+ while (GNUNET_YES == replay_req_remove_client (grp, client));
+
+ if (NULL == grp->clients_head)
+ { /* Last client disconnected. */
+ cleanup_group (grp);
+ }
+}
+
+
+/**
+ * Service started.
+ *
+ * @param cls closure
+ * @param server the initialized server
+ * @param cfg configuration to use
+ */
+static void
+run (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *c,
+ struct GNUNET_SERVICE_Handle *svc)
+{
+ cfg = c;
+ service = svc;
+ GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer);
+
+ stats = GNUNET_STATISTICS_create ("multicast", cfg);
+ origins = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ members = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ group_members = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ channels_in = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ channels_out = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ replay_req_cadet = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ replay_req_client = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+
+ cadet = GNUNET_CADET_connect (cfg);
+
+ GNUNET_assert (NULL != cadet);
+
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+}
+
+
+/**
+ * Define "main" method using service macro.
+ */
+GNUNET_SERVICE_MAIN
+("multicast",
+ GNUNET_SERVICE_OPTION_NONE,
+ &run,
+ &client_notify_connect,
+ &client_notify_disconnect,
+ NULL,
+ GNUNET_MQ_hd_fixed_size (client_origin_start,
+ GNUNET_MESSAGE_TYPE_MULTICAST_ORIGIN_START,
+ struct MulticastOriginStartMessage,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_member_join,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MEMBER_JOIN,
+ struct MulticastMemberJoinMessage,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_join_decision,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION,
+ struct MulticastJoinDecisionMessageHeader,
+ NULL),
+ GNUNET_MQ_hd_fixed_size (client_part_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_PART_REQUEST,
+ struct GNUNET_MessageHeader,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_multicast_message,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE,
+ struct GNUNET_MULTICAST_MessageHeader,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_multicast_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST,
+ struct GNUNET_MULTICAST_RequestHeader,
+ NULL),
+ GNUNET_MQ_hd_fixed_size (client_replay_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST,
+ struct MulticastReplayRequestMessage,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_replay_response,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE,
+ struct MulticastReplayResponseMessage,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_replay_response_end,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE_END,
+ struct MulticastReplayResponseMessage,
+ NULL));
+
+/* end of gnunet-service-multicast.c */
diff --git a/src/multicast/multicast.conf.in b/src/multicast/multicast.conf.in
new file mode 100644
index 0000000..97a5413
--- /dev/null
+++ b/src/multicast/multicast.conf.in
@@ -0,0 +1,22 @@
+[multicast]
+START_ON_DEMAND = @START_ON_DEMAND@
+BINARY = gnunet-service-multicast
+
+UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock
+UNIX_MATCH_UID = YES
+UNIX_MATCH_GID = YES
+
+@UNIXONLY@PORT = 2109
+HOSTNAME = localhost
+ACCEPT_FROM = 127.0.0.1;
+ACCEPT_FROM6 = ::1;
+
+# DISABLE_SOCKET_FORWARDING = NO
+# USERNAME =
+# MAXBUF =
+# TIMEOUT =
+# DISABLEV6 =
+# BINDTO =
+# REJECT_FROM =
+# REJECT_FROM6 =
+# PREFIX =
diff --git a/src/multicast/multicast.h b/src/multicast/multicast.h
new file mode 100644
index 0000000..8a3ca14
--- /dev/null
+++ b/src/multicast/multicast.h
@@ -0,0 +1,303 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013 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
+*/
+
+/**
+ * @file multicast/multicast.h
+ * @brief multicast IPC messages
+ * @author Christian Grothoff
+ * @author Gabor X Toth
+ */
+#ifndef MULTICAST_H
+#define MULTICAST_H
+
+#include "platform.h"
+#include "gnunet_multicast_service.h"
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * Header of a join request sent to the origin or another member.
+ */
+struct MulticastJoinRequestMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * ECC signature of the rest of the fields of the join request.
+ *
+ * Signature must match the public key of the joining member.
+ */
+ struct GNUNET_CRYPTO_EcdsaSignature signature;
+
+ /**
+ * Purpose for the signature and size of the signed data.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Public key of the target group.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key;
+
+ /**
+ * Public key of the joining member.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+ /**
+ * Peer identity of the joining member.
+ */
+ struct GNUNET_PeerIdentity peer;
+
+ /* Followed by struct GNUNET_MessageHeader join_message */
+};
+
+
+/**
+ * Header of a join decision message sent to a peer requesting join.
+ */
+struct MulticastJoinDecisionMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * #GNUNET_YES if the peer was admitted
+ * #GNUNET_NO if entry was refused,
+ * #GNUNET_SYSERR if the request could not be answered.
+ */
+ int32_t is_admitted;
+
+ /**
+ * Number of relays given.
+ */
+ uint32_t relay_count;
+
+ /* Followed by relay_count peer identities */
+
+ /* Followed by the join response message */
+};
+
+
+/**
+ * Header added to a struct MulticastJoinDecisionMessage
+ * when sent between the client and service.
+ */
+struct MulticastJoinDecisionMessageHeader
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * C->S: Peer to send the join decision to.
+ * S->C: Peer we received the join decision from.
+ */
+ struct GNUNET_PeerIdentity peer;
+
+ /**
+ * C->S: Public key of the member requesting join.
+ * S->C: Unused.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+ /* Followed by struct MulticastJoinDecisionMessage */
+};
+
+
+/**
+ * Message sent from the client to the service to notify the service
+ * about the result of a membership test.
+ */
+struct MulticastMembershipTestResultMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_MEMBERSHIP_TEST_RESULT
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Unique ID that identifies the associated membership test.
+ */
+ uint32_t uid;
+
+ /**
+ * #GNUNET_YES if the peer is a member
+ * #GNUNET_NO if peer is not a member,
+ * #GNUNET_SYSERR if the test could not be answered.
+ */
+ int32_t is_admitted;
+};
+
+
+/**
+ * Message sent from the client to the service OR the service to the
+ * client asking for a message fragment to be replayed.
+ */
+struct MulticastReplayRequestMessage
+{
+
+ /**
+ * The message type should be
+ * #GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST.
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * S->C: Public key of the member requesting replay.
+ * C->S: Unused.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+ /**
+ * ID of the message that is being requested.
+ */
+ uint64_t fragment_id;
+
+ /**
+ * ID of the message that is being requested.
+ */
+ uint64_t message_id;
+
+ /**
+ * Offset of the fragment that is being requested.
+ */
+ uint64_t fragment_offset;
+
+ /**
+ * Additional flags for the request.
+ */
+ uint64_t flags;
+
+ /**
+ * Replay request ID.
+ */
+ uint32_t uid;
+};
+
+
+/**
+ * Message sent from the client to the service to give the service
+ * a replayed message.
+ */
+struct MulticastReplayResponseMessage
+{
+
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE
+ * or GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE_END
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * ID of the message that is being requested.
+ */
+ uint64_t fragment_id;
+
+ /**
+ * ID of the message that is being requested.
+ */
+ uint64_t message_id;
+
+ /**
+ * Offset of the fragment that is being requested.
+ */
+ uint64_t fragment_offset;
+
+ /**
+ * Additional flags for the request.
+ */
+ uint64_t flags;
+
+ /**
+ * An `enum GNUNET_MULTICAST_ReplayErrorCode` identifying issues (in NBO).
+ */
+ int32_t error_code;
+
+ /* followed by replayed message */
+};
+
+
+/**
+ * Message sent from the client to the service to notify the service
+ * about the starting of a multicast group with this peers as its origin.
+ */
+struct MulticastOriginStartMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_ORIGIN_START
+ */
+ struct GNUNET_MessageHeader header;
+
+ /**
+ * Always zero.
+ */
+ uint32_t reserved;
+
+ /**
+ * Private, non-ephemeral key for the multicast group.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey group_key;
+
+ /**
+ * Last fragment ID sent to the group, used to continue counting fragments if
+ * we resume operating * a group.
+ */
+ uint64_t max_fragment_id;
+};
+
+
+struct MulticastMemberJoinMessage
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_MULTICAST_MEMBER_JOIN
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t relay_count GNUNET_PACKED;
+
+ struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key;
+
+ struct GNUNET_CRYPTO_EcdsaPrivateKey member_key;
+
+ struct GNUNET_PeerIdentity origin;
+
+ /* Followed by struct GNUNET_PeerIdentity relays[relay_count] */
+
+ /* Followed by struct GNUNET_MessageHeader join_msg */
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+#endif
+/* end of multicast.h */
diff --git a/src/multicast/multicast_api.c b/src/multicast/multicast_api.c
new file mode 100644
index 0000000..e5e8302
--- /dev/null
+++ b/src/multicast/multicast_api.c
@@ -0,0 +1,1399 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013 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
+*/
+
+/**
+ * @file multicast/multicast_api.c
+ * @brief Multicast service; implements multicast groups using CADET connections.
+ * @author Christian Grothoff
+ * @author Gabor X Toth
+ */
+
+#include "platform.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_multicast_service.h"
+#include "multicast.h"
+
+#define LOG(kind,...) GNUNET_log_from (kind, "multicast-api",__VA_ARGS__)
+
+
+/**
+ * Handle for a request to send a message to all multicast group members
+ * (from the origin).
+ */
+struct GNUNET_MULTICAST_OriginTransmitHandle
+{
+ GNUNET_MULTICAST_OriginTransmitNotify notify;
+ void *notify_cls;
+ struct GNUNET_MULTICAST_Origin *origin;
+
+ uint64_t message_id;
+ uint64_t group_generation;
+ uint64_t fragment_offset;
+};
+
+
+/**
+ * Handle for a message to be delivered from a member to the origin.
+ */
+struct GNUNET_MULTICAST_MemberTransmitHandle
+{
+ GNUNET_MULTICAST_MemberTransmitNotify notify;
+ void *notify_cls;
+ struct GNUNET_MULTICAST_Member *member;
+
+ uint64_t request_id;
+ uint64_t fragment_offset;
+};
+
+
+struct GNUNET_MULTICAST_Group
+{
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Client connection to the service.
+ */
+ struct GNUNET_MQ_Handle *mq;
+
+ /**
+ * Message to send on connect.
+ */
+ struct GNUNET_MQ_Envelope *connect_env;
+
+ /**
+ * Time to wait until we try to reconnect on failure.
+ */
+ struct GNUNET_TIME_Relative reconnect_delay;
+
+ /**
+ * Task for reconnecting when the listener fails.
+ */
+ struct GNUNET_SCHEDULER_Task *reconnect_task;
+
+ GNUNET_MULTICAST_JoinRequestCallback join_req_cb;
+ GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb;
+ GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb;
+ GNUNET_MULTICAST_MessageCallback message_cb;
+ void *cb_cls;
+
+ /**
+ * Function called after disconnected from the service.
+ */
+ GNUNET_ContinuationCallback disconnect_cb;
+
+ /**
+ * Closure for @a disconnect_cb.
+ */
+ void *disconnect_cls;
+
+ /**
+ * Are we currently transmitting a message?
+ */
+ uint8_t in_transmit;
+
+ /**
+ * Number of MULTICAST_FRAGMENT_ACK messages we are still waiting for.
+ */
+ uint8_t acks_pending;
+
+ /**
+ * Is this the origin or a member?
+ */
+ uint8_t is_origin;
+
+ /**
+ * Is this channel in the process of disconnecting from the service?
+ * #GNUNET_YES or #GNUNET_NO
+ */
+ uint8_t is_disconnecting;
+};
+
+
+/**
+ * Handle for the origin of a multicast group.
+ */
+struct GNUNET_MULTICAST_Origin
+{
+ struct GNUNET_MULTICAST_Group grp;
+ struct GNUNET_MULTICAST_OriginTransmitHandle tmit;
+
+ GNUNET_MULTICAST_RequestCallback request_cb;
+};
+
+
+/**
+ * Handle for a multicast group member.
+ */
+struct GNUNET_MULTICAST_Member
+{
+ struct GNUNET_MULTICAST_Group grp;
+ struct GNUNET_MULTICAST_MemberTransmitHandle tmit;
+
+ GNUNET_MULTICAST_JoinDecisionCallback join_dcsn_cb;
+
+ /**
+ * Replay fragment -> struct GNUNET_MULTICAST_MemberReplayHandle *
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *replay_reqs;
+
+ uint64_t next_fragment_id;
+};
+
+
+/**
+ * Handle that identifies a join request.
+ *
+ * Used to match calls to #GNUNET_MULTICAST_JoinRequestCallback to the
+ * corresponding calls to #GNUNET_MULTICAST_join_decision().
+ */
+struct GNUNET_MULTICAST_JoinHandle
+{
+ struct GNUNET_MULTICAST_Group *group;
+
+ /**
+ * Public key of the member requesting join.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+ /**
+ * Peer identity of the member requesting join.
+ */
+ struct GNUNET_PeerIdentity peer;
+};
+
+
+/**
+ * Opaque handle to a replay request from the multicast service.
+ */
+struct GNUNET_MULTICAST_ReplayHandle
+{
+ struct GNUNET_MULTICAST_Group *grp;
+ struct MulticastReplayRequestMessage req;
+};
+
+
+/**
+ * Handle for a replay request.
+ */
+struct GNUNET_MULTICAST_MemberReplayHandle
+{
+};
+
+
+static void
+origin_to_all (struct GNUNET_MULTICAST_Origin *orig);
+
+static void
+member_to_origin (struct GNUNET_MULTICAST_Member *mem);
+
+
+/**
+ * Check join request message.
+ */
+static int
+check_group_join_request (void *cls,
+ const struct MulticastJoinRequestMessage *jreq)
+{
+ uint16_t size = ntohs (jreq->header.size);
+
+ if (sizeof (*jreq) == size)
+ return GNUNET_OK;
+
+ if (sizeof (*jreq) + sizeof (struct GNUNET_MessageHeader) <= size)
+ return GNUNET_OK;
+
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Receive join request from service.
+ */
+static void
+handle_group_join_request (void *cls,
+ const struct MulticastJoinRequestMessage *jreq)
+{
+ struct GNUNET_MULTICAST_Group *grp = cls;
+ struct GNUNET_MULTICAST_JoinHandle *jh;
+ const struct GNUNET_MessageHeader *jmsg = NULL;
+
+ if (NULL == grp)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (NULL == grp->join_req_cb)
+ return;
+
+ if (sizeof (*jreq) + sizeof (*jmsg) <= ntohs (jreq->header.size))
+ jmsg = (const struct GNUNET_MessageHeader *) &jreq[1];
+
+ jh = GNUNET_malloc (sizeof (*jh));
+ jh->group = grp;
+ jh->member_pub_key = jreq->member_pub_key;
+ jh->peer = jreq->peer;
+ grp->join_req_cb (grp->cb_cls, &jreq->member_pub_key, jmsg, jh);
+
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+}
+
+
+/**
+ * Check multicast message.
+ */
+static int
+check_group_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *mmsg)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Receive multicast message from service.
+ */
+static void
+handle_group_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *mmsg)
+{
+ struct GNUNET_MULTICAST_Group *grp = cls;
+
+ if (GNUNET_YES == grp->is_disconnecting)
+ return;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Calling message callback with a message of size %u.\n",
+ ntohs (mmsg->header.size));
+
+ if (NULL != grp->message_cb)
+ grp->message_cb (grp->cb_cls, mmsg);
+
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+}
+
+
+/**
+ * Receive message/request fragment acknowledgement from service.
+ */
+static void
+handle_group_fragment_ack (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct GNUNET_MULTICAST_Group *grp = cls;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Got fragment ACK. in_transmit=%u, acks_pending=%u\n",
+ grp, grp->in_transmit, grp->acks_pending);
+
+ if (0 == grp->acks_pending)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Ignoring extraneous fragment ACK.\n", grp);
+ return;
+ }
+ grp->acks_pending--;
+
+ if (GNUNET_YES != grp->in_transmit)
+ return;
+
+ if (GNUNET_YES == grp->is_origin)
+ origin_to_all ((struct GNUNET_MULTICAST_Origin *) grp);
+ else
+ member_to_origin ((struct GNUNET_MULTICAST_Member *) grp);
+
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+}
+
+
+/**
+ * Check unicast request.
+ */
+static int
+check_origin_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Origin receives unicast request from a member.
+ */
+static void
+handle_origin_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ struct GNUNET_MULTICAST_Group *grp;
+ struct GNUNET_MULTICAST_Origin *orig = cls;
+ grp = &orig->grp;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Calling request callback with a request of size %u.\n",
+ ntohs (req->header.size));
+
+ if (NULL != orig->request_cb)
+ orig->request_cb (grp->cb_cls, req);
+
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+}
+
+
+/**
+ * Receive multicast replay request from service.
+ */
+static void
+handle_group_replay_request (void *cls,
+ const struct MulticastReplayRequestMessage *rep)
+
+{
+ struct GNUNET_MULTICAST_Group *grp = cls;
+
+ if (GNUNET_YES == grp->is_disconnecting)
+ return;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got replay request.\n");
+
+ if (0 != rep->fragment_id)
+ {
+ if (NULL != grp->replay_frag_cb)
+ {
+ struct GNUNET_MULTICAST_ReplayHandle * rh = GNUNET_malloc (sizeof (*rh));
+ rh->grp = grp;
+ rh->req = *rep;
+ grp->replay_frag_cb (grp->cb_cls, &rep->member_pub_key,
+ GNUNET_ntohll (rep->fragment_id),
+ GNUNET_ntohll (rep->flags), rh);
+ }
+ }
+ else if (0 != rep->message_id)
+ {
+ if (NULL != grp->replay_msg_cb)
+ {
+ struct GNUNET_MULTICAST_ReplayHandle * rh = GNUNET_malloc (sizeof (*rh));
+ rh->grp = grp;
+ rh->req = *rep;
+ grp->replay_msg_cb (grp->cb_cls, &rep->member_pub_key,
+ GNUNET_ntohll (rep->message_id),
+ GNUNET_ntohll (rep->fragment_offset),
+ GNUNET_ntohll (rep->flags), rh);
+ }
+ }
+
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+}
+
+
+/**
+ * Check replay response.
+ */
+static int
+check_member_replay_response (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ uint16_t size = ntohs (res->header.size);
+
+ if (sizeof (*res) == size)
+ return GNUNET_OK;
+
+ if (sizeof (*res) + sizeof (struct GNUNET_MULTICAST_MessageHeader) <= size)
+ return GNUNET_OK;
+
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Receive replay response from service.
+ */
+static void
+handle_member_replay_response (void *cls,
+ const struct MulticastReplayResponseMessage *res)
+{
+ struct GNUNET_MULTICAST_Group *grp;
+ struct GNUNET_MULTICAST_Member *mem = cls;
+ grp = &mem->grp;
+
+ if (GNUNET_YES == grp->is_disconnecting)
+ return;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got replay response.\n");
+
+ // FIXME: return result
+}
+
+
+/**
+ * Check join decision.
+ */
+static int
+check_member_join_decision (void *cls,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ return GNUNET_OK; // checked in handle below
+}
+
+
+/**
+ * Member receives join decision.
+ */
+static void
+handle_member_join_decision (void *cls,
+ const struct MulticastJoinDecisionMessageHeader *hdcsn)
+{
+ struct GNUNET_MULTICAST_Group *grp;
+ struct GNUNET_MULTICAST_Member *mem = cls;
+ grp = &mem->grp;
+
+ const struct MulticastJoinDecisionMessage *
+ dcsn = (const struct MulticastJoinDecisionMessage *) &hdcsn[1];
+
+ uint16_t dcsn_size = ntohs (dcsn->header.size);
+ int is_admitted = ntohl (dcsn->is_admitted);
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Member got join decision from multicast: %d\n",
+ mem, is_admitted);
+
+ const struct GNUNET_MessageHeader *join_resp = NULL;
+ uint16_t join_resp_size = 0;
+
+ uint16_t relay_count = ntohl (dcsn->relay_count);
+ const struct GNUNET_PeerIdentity *relays = NULL;
+ uint16_t relay_size = relay_count * sizeof (*relays);
+ if (0 < relay_count)
+ {
+ if (dcsn_size < sizeof (*dcsn) + relay_size)
+ {
+ GNUNET_break_op (0);
+ is_admitted = GNUNET_SYSERR;
+ }
+ else
+ {
+ relays = (struct GNUNET_PeerIdentity *) &dcsn[1];
+ }
+ }
+
+ if (sizeof (*dcsn) + relay_size + sizeof (*join_resp) <= dcsn_size)
+ {
+ join_resp = (const struct GNUNET_MessageHeader *) ((char *) &dcsn[1] + relay_size);
+ join_resp_size = ntohs (join_resp->size);
+ }
+ if (dcsn_size < sizeof (*dcsn) + relay_size + join_resp_size)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Received invalid join decision message from multicast: %u < %u + %u + %u\n",
+ dcsn_size , sizeof (*dcsn), relay_size, join_resp_size);
+ GNUNET_break_op (0);
+ is_admitted = GNUNET_SYSERR;
+ }
+
+ if (NULL != mem->join_dcsn_cb)
+ mem->join_dcsn_cb (grp->cb_cls, is_admitted, &hdcsn->peer,
+ relay_count, relays, join_resp);
+
+ // FIXME:
+ //if (GNUNET_YES != is_admitted)
+ // GNUNET_MULTICAST_member_part (mem);
+
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+}
+
+
+static void
+group_cleanup (struct GNUNET_MULTICAST_Group *grp)
+{
+ if (NULL != grp->connect_env)
+ {
+ GNUNET_MQ_discard (grp->connect_env);
+ grp->connect_env = NULL;
+ }
+ if (NULL != grp->mq)
+ {
+ GNUNET_MQ_destroy (grp->mq);
+ grp->mq = NULL;
+ }
+ if (NULL != grp->disconnect_cb)
+ {
+ grp->disconnect_cb (grp->disconnect_cls);
+ grp->disconnect_cb = NULL;
+ }
+ GNUNET_free (grp);
+}
+
+
+static void
+handle_group_part_ack (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct GNUNET_MULTICAST_Group *grp = cls;
+
+ group_cleanup (grp);
+}
+
+
+/**
+ * Function to call with the decision made for a join request.
+ *
+ * Must be called once and only once in response to an invocation of the
+ * #GNUNET_MULTICAST_JoinRequestCallback.
+ *
+ * @param join
+ * Join request handle.
+ * @param is_admitted
+ * #GNUNET_YES if the join is approved,
+ * #GNUNET_NO if it is disapproved,
+ * #GNUNET_SYSERR if we cannot answer the request.
+ * @param relay_count
+ * Number of relays given.
+ * @param relays
+ * Array of suggested peers that might be useful relays to use
+ * when joining the multicast group (essentially a list of peers that
+ * are already part of the multicast group and might thus be willing
+ * to help with routing). If empty, only this local peer (which must
+ * be the multicast origin) is a good candidate for building the
+ * multicast tree. Note that it is unnecessary to specify our own
+ * peer identity in this array.
+ * @param join_resp
+ * Message to send in response to the joining peer;
+ * can also be used to redirect the peer to a different group at the
+ * application layer; this response is to be transmitted to the
+ * peer that issued the request even if admission is denied.
+ */
+struct GNUNET_MULTICAST_ReplayHandle *
+GNUNET_MULTICAST_join_decision (struct GNUNET_MULTICAST_JoinHandle *join,
+ int is_admitted,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_resp)
+{
+ struct GNUNET_MULTICAST_Group *grp = join->group;
+ uint16_t join_resp_size = (NULL != join_resp) ? ntohs (join_resp->size) : 0;
+ uint16_t relay_size = relay_count * sizeof (*relays);
+
+ struct MulticastJoinDecisionMessageHeader *hdcsn;
+ struct MulticastJoinDecisionMessage *dcsn;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (hdcsn, sizeof (*dcsn) + relay_size + join_resp_size,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION);
+ hdcsn->member_pub_key = join->member_pub_key;
+ hdcsn->peer = join->peer;
+
+ dcsn = (struct MulticastJoinDecisionMessage *) &hdcsn[1];
+ dcsn->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION);
+ dcsn->header.size = htons (sizeof (*dcsn) + relay_size + join_resp_size);
+ dcsn->is_admitted = htonl (is_admitted);
+ dcsn->relay_count = htonl (relay_count);
+ if (0 < relay_size)
+ GNUNET_memcpy (&dcsn[1], relays, relay_size);
+ if (0 < join_resp_size)
+ GNUNET_memcpy (((char *) &dcsn[1]) + relay_size, join_resp, join_resp_size);
+
+ GNUNET_MQ_send (grp->mq, env);
+ GNUNET_free (join);
+ return NULL;
+}
+
+
+/**
+ * Replay a message fragment for the multicast group.
+ *
+ * @param rh
+ * Replay handle identifying which replay operation was requested.
+ * @param msg
+ * Replayed message fragment, NULL if not found / an error occurred.
+ * @param ec
+ * Error code. See enum GNUNET_MULTICAST_ReplayErrorCode
+ * If not #GNUNET_MULTICAST_REC_OK, the replay handle is invalidated.
+ */
+void
+GNUNET_MULTICAST_replay_response (struct GNUNET_MULTICAST_ReplayHandle *rh,
+ const struct GNUNET_MessageHeader *msg,
+ enum GNUNET_MULTICAST_ReplayErrorCode ec)
+{
+ uint8_t msg_size = (NULL != msg) ? ntohs (msg->size) : 0;
+ struct MulticastReplayResponseMessage *res;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (res, msg_size,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE);
+ res->fragment_id = rh->req.fragment_id;
+ res->message_id = rh->req.message_id;
+ res->fragment_offset = rh->req.fragment_offset;
+ res->flags = rh->req.flags;
+ res->error_code = htonl (ec);
+
+ if (GNUNET_MULTICAST_REC_OK == ec)
+ {
+ GNUNET_assert (NULL != msg);
+ GNUNET_memcpy (&res[1], msg, msg_size);
+ }
+
+ GNUNET_MQ_send (rh->grp->mq, env);
+
+ if (GNUNET_MULTICAST_REC_OK != ec)
+ GNUNET_free (rh);
+}
+
+
+/**
+ * Indicate the end of the replay session.
+ *
+ * Invalidates the replay handle.
+ *
+ * @param rh
+ * Replay session to end.
+ */
+void
+GNUNET_MULTICAST_replay_response_end (struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ struct MulticastReplayResponseMessage *end;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (end, GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE_END);
+
+ end->fragment_id = rh->req.fragment_id;
+ end->message_id = rh->req.message_id;
+ end->fragment_offset = rh->req.fragment_offset;
+ end->flags = rh->req.flags;
+
+ GNUNET_MQ_send (rh->grp->mq, env);
+ GNUNET_free (rh);
+}
+
+
+/**
+ * Replay a message for the multicast group.
+ *
+ * @param rh
+ * Replay handle identifying which replay operation was requested.
+ * @param notify
+ * Function to call to get the message.
+ * @param notify_cls
+ * Closure for @a notify.
+ */
+void
+GNUNET_MULTICAST_replay_response2 (struct GNUNET_MULTICAST_ReplayHandle *rh,
+ GNUNET_MULTICAST_ReplayTransmitNotify notify,
+ void *notify_cls)
+{
+}
+
+
+static void
+origin_connect (struct GNUNET_MULTICAST_Origin *orig);
+
+
+static void
+origin_reconnect (void *cls)
+{
+ origin_connect (cls);
+}
+
+
+/**
+ * Origin client disconnected from service.
+ *
+ * Reconnect after backoff period.
+ */
+static void
+origin_disconnected (void *cls, enum GNUNET_MQ_Error error)
+{
+ struct GNUNET_MULTICAST_Origin *orig = cls;
+ struct GNUNET_MULTICAST_Group *grp = &orig->grp;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Origin client disconnected (%d), re-connecting\n",
+ (int) error);
+ if (NULL != grp->mq)
+ {
+ GNUNET_MQ_destroy (grp->mq);
+ grp->mq = NULL;
+ }
+
+ grp->reconnect_task = GNUNET_SCHEDULER_add_delayed (grp->reconnect_delay,
+ origin_reconnect,
+ orig);
+ grp->reconnect_delay = GNUNET_TIME_STD_BACKOFF (grp->reconnect_delay);
+}
+
+
+/**
+ * Connect to service as origin.
+ */
+static void
+origin_connect (struct GNUNET_MULTICAST_Origin *orig)
+{
+ struct GNUNET_MULTICAST_Group *grp = &orig->grp;
+
+ struct GNUNET_MQ_MessageHandler handlers[] = {
+ GNUNET_MQ_hd_var_size (group_message,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE,
+ struct GNUNET_MULTICAST_MessageHeader,
+ grp),
+ GNUNET_MQ_hd_var_size (origin_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST,
+ struct GNUNET_MULTICAST_RequestHeader,
+ orig),
+ GNUNET_MQ_hd_fixed_size (group_fragment_ack,
+ GNUNET_MESSAGE_TYPE_MULTICAST_FRAGMENT_ACK,
+ struct GNUNET_MessageHeader,
+ grp),
+ GNUNET_MQ_hd_var_size (group_join_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST,
+ struct MulticastJoinRequestMessage,
+ grp),
+ GNUNET_MQ_hd_fixed_size (group_part_ack,
+ GNUNET_MESSAGE_TYPE_MULTICAST_PART_ACK,
+ struct GNUNET_MessageHeader,
+ grp),
+ GNUNET_MQ_hd_fixed_size (group_replay_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST,
+ struct MulticastReplayRequestMessage,
+ grp),
+ GNUNET_MQ_handler_end ()
+ };
+
+ grp->mq = GNUNET_CLIENT_connect (grp->cfg, "multicast",
+ handlers, origin_disconnected, orig);
+ GNUNET_assert (NULL != grp->mq);
+ GNUNET_MQ_send_copy (grp->mq, grp->connect_env);
+}
+
+
+/**
+ * Start a multicast group.
+ *
+ * Will advertise the origin in the P2P overlay network under the respective
+ * public key so that other peer can find this peer to join it. Peers that
+ * issue GNUNET_MULTICAST_member_join() can then transmit a join request to
+ * either an existing group member or to the origin. If the joining is
+ * approved, the member is cleared for @e replay and will begin to receive
+ * messages transmitted to the group. If joining is disapproved, the failed
+ * candidate will be given a response. Members in the group can send messages
+ * to the origin (one at a time).
+ *
+ * @param cfg
+ * Configuration to use.
+ * @param priv_key
+ * ECC key that will be used to sign messages for this
+ * multicast session; public key is used to identify the multicast group;
+ * @param max_fragment_id
+ * Maximum fragment ID already sent to the group.
+ * 0 for a new group.
+ * @param join_request_cb
+ * Function called to approve / disapprove joining of a peer.
+ * @param replay_frag_cb
+ * Function that can be called to replay a message fragment.
+ * @param replay_msg_cb
+ * Function that can be called to replay a message.
+ * @param request_cb
+ * Function called with message fragments from group members.
+ * @param message_cb
+ * Function called with the message fragments sent to the
+ * network by GNUNET_MULTICAST_origin_to_all(). These message fragments
+ * should be stored for answering replay requests later.
+ * @param cls
+ * Closure for the various callbacks that follow.
+ *
+ * @return Handle for the origin, NULL on error.
+ */
+struct GNUNET_MULTICAST_Origin *
+GNUNET_MULTICAST_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *priv_key,
+ uint64_t max_fragment_id,
+ GNUNET_MULTICAST_JoinRequestCallback join_request_cb,
+ GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb,
+ GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb,
+ GNUNET_MULTICAST_RequestCallback request_cb,
+ GNUNET_MULTICAST_MessageCallback message_cb,
+ void *cls)
+{
+ struct GNUNET_MULTICAST_Origin *orig = GNUNET_malloc (sizeof (*orig));
+ struct GNUNET_MULTICAST_Group *grp = &orig->grp;
+
+ struct MulticastOriginStartMessage *start;
+ grp->connect_env = GNUNET_MQ_msg (start,
+ GNUNET_MESSAGE_TYPE_MULTICAST_ORIGIN_START);
+ start->max_fragment_id = max_fragment_id;
+ start->group_key = *priv_key;
+
+ grp->cfg = cfg;
+ grp->is_origin = GNUNET_YES;
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+
+ grp->cb_cls = cls;
+ grp->join_req_cb = join_request_cb;
+ grp->replay_frag_cb = replay_frag_cb;
+ grp->replay_msg_cb = replay_msg_cb;
+ grp->message_cb = message_cb;
+
+ orig->request_cb = request_cb;
+
+ origin_connect (orig);
+ return orig;
+}
+
+
+/**
+ * Stop a multicast group.
+ *
+ * @param origin
+ * Multicast group to stop.
+ */
+void
+GNUNET_MULTICAST_origin_stop (struct GNUNET_MULTICAST_Origin *orig,
+ GNUNET_ContinuationCallback stop_cb,
+ void *stop_cls)
+{
+ struct GNUNET_MULTICAST_Group *grp = &orig->grp;
+ struct GNUNET_MQ_Envelope *env;
+
+ grp->is_disconnecting = GNUNET_YES;
+ grp->disconnect_cb = stop_cb;
+ grp->disconnect_cls = stop_cls;
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_PART_REQUEST);
+ GNUNET_MQ_send (grp->mq, env);
+}
+
+
+static void
+origin_to_all (struct GNUNET_MULTICAST_Origin *orig)
+{
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "%p origin_to_all()\n", orig);
+ struct GNUNET_MULTICAST_Group *grp = &orig->grp;
+ struct GNUNET_MULTICAST_OriginTransmitHandle *tmit = &orig->tmit;
+ GNUNET_assert (GNUNET_YES == grp->in_transmit);
+
+ size_t buf_size = GNUNET_MULTICAST_FRAGMENT_MAX_SIZE;
+ struct GNUNET_MULTICAST_MessageHeader *msg;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (msg, buf_size - sizeof(*msg),
+ GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE);
+
+ int ret = tmit->notify (tmit->notify_cls, &buf_size, &msg[1]);
+
+ if (! (GNUNET_YES == ret || GNUNET_NO == ret)
+ || GNUNET_MULTICAST_FRAGMENT_MAX_SIZE < buf_size)
+ {
+ LOG (GNUNET_ERROR_TYPE_ERROR,
+ "%p OriginTransmitNotify() returned error or invalid message size.\n",
+ orig);
+ /* FIXME: handle error */
+ GNUNET_MQ_discard (env);
+ return;
+ }
+
+ if (GNUNET_NO == ret && 0 == buf_size)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "%p OriginTransmitNotify() - transmission paused.\n", orig);
+ GNUNET_MQ_discard (env);
+ return; /* Transmission paused. */
+ }
+
+ msg->header.size = htons (sizeof (*msg) + buf_size);
+ msg->message_id = GNUNET_htonll (tmit->message_id);
+ msg->group_generation = tmit->group_generation;
+ msg->fragment_offset = GNUNET_htonll (tmit->fragment_offset);
+ tmit->fragment_offset += sizeof (*msg) + buf_size;
+
+ grp->acks_pending++;
+ GNUNET_MQ_send (grp->mq, env);
+
+ if (GNUNET_YES == ret)
+ grp->in_transmit = GNUNET_NO;
+}
+
+
+/**
+ * Send a message to the multicast group.
+ *
+ * @param orig
+ * Handle to the multicast group.
+ * @param message_id
+ * Application layer ID for the message. Opaque to multicast.
+ * @param group_generation
+ * Group generation of the message.
+ * Documented in struct GNUNET_MULTICAST_MessageHeader.
+ * @param notify
+ * Function to call to get the message.
+ * @param notify_cls
+ * Closure for @a notify.
+ *
+ * @return Message handle on success,
+ * NULL on error (i.e. another request is already pending).
+ */
+struct GNUNET_MULTICAST_OriginTransmitHandle *
+GNUNET_MULTICAST_origin_to_all (struct GNUNET_MULTICAST_Origin *orig,
+ uint64_t message_id,
+ uint64_t group_generation,
+ GNUNET_MULTICAST_OriginTransmitNotify notify,
+ void *notify_cls)
+{
+ struct GNUNET_MULTICAST_Group *grp = &orig->grp;
+ if (GNUNET_YES == grp->in_transmit)
+ return NULL;
+ grp->in_transmit = GNUNET_YES;
+
+ struct GNUNET_MULTICAST_OriginTransmitHandle *tmit = &orig->tmit;
+ tmit->origin = orig;
+ tmit->message_id = message_id;
+ tmit->fragment_offset = 0;
+ tmit->group_generation = group_generation;
+ tmit->notify = notify;
+ tmit->notify_cls = notify_cls;
+
+ origin_to_all (orig);
+ return tmit;
+}
+
+
+/**
+ * Resume message transmission to multicast group.
+ *
+ * @param th
+ * Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_origin_to_all_resume (struct GNUNET_MULTICAST_OriginTransmitHandle *th)
+{
+ struct GNUNET_MULTICAST_Group *grp = &th->origin->grp;
+ if (0 != grp->acks_pending || GNUNET_YES != grp->in_transmit)
+ return;
+ origin_to_all (th->origin);
+}
+
+
+/**
+ * Cancel request for message transmission to multicast group.
+ *
+ * @param th
+ * Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_origin_to_all_cancel (struct GNUNET_MULTICAST_OriginTransmitHandle *th)
+{
+ th->origin->grp.in_transmit = GNUNET_NO;
+}
+
+
+static void
+member_connect (struct GNUNET_MULTICAST_Member *mem);
+
+
+static void
+member_reconnect (void *cls)
+{
+ member_connect (cls);
+}
+
+
+/**
+ * Member client disconnected from service.
+ *
+ * Reconnect after backoff period.
+ */
+static void
+member_disconnected (void *cls, enum GNUNET_MQ_Error error)
+{
+ struct GNUNET_MULTICAST_Member *mem = cls;
+ struct GNUNET_MULTICAST_Group *grp = &mem->grp;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Member client disconnected (%d), re-connecting\n",
+ (int) error);
+ GNUNET_MQ_destroy (grp->mq);
+ grp->mq = NULL;
+
+ grp->reconnect_task = GNUNET_SCHEDULER_add_delayed (grp->reconnect_delay,
+ member_reconnect,
+ mem);
+ grp->reconnect_delay = GNUNET_TIME_STD_BACKOFF (grp->reconnect_delay);
+}
+
+
+/**
+ * Connect to service as member.
+ */
+static void
+member_connect (struct GNUNET_MULTICAST_Member *mem)
+{
+ struct GNUNET_MULTICAST_Group *grp = &mem->grp;
+
+ struct GNUNET_MQ_MessageHandler handlers[] = {
+ GNUNET_MQ_hd_var_size (group_message,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE,
+ struct GNUNET_MULTICAST_MessageHeader,
+ grp),
+ GNUNET_MQ_hd_fixed_size (group_fragment_ack,
+ GNUNET_MESSAGE_TYPE_MULTICAST_FRAGMENT_ACK,
+ struct GNUNET_MessageHeader,
+ grp),
+ GNUNET_MQ_hd_var_size (group_join_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST,
+ struct MulticastJoinRequestMessage,
+ grp),
+ GNUNET_MQ_hd_var_size (member_join_decision,
+ GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION,
+ struct MulticastJoinDecisionMessageHeader,
+ mem),
+ GNUNET_MQ_hd_fixed_size (group_part_ack,
+ GNUNET_MESSAGE_TYPE_MULTICAST_PART_ACK,
+ struct GNUNET_MessageHeader,
+ grp),
+ GNUNET_MQ_hd_fixed_size (group_replay_request,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST,
+ struct MulticastReplayRequestMessage,
+ grp),
+ GNUNET_MQ_hd_var_size (member_replay_response,
+ GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE,
+ struct MulticastReplayResponseMessage,
+ mem),
+ GNUNET_MQ_handler_end ()
+ };
+
+ grp->mq = GNUNET_CLIENT_connect (grp->cfg, "multicast",
+ handlers, member_disconnected, mem);
+ GNUNET_assert (NULL != grp->mq);
+ GNUNET_MQ_send_copy (grp->mq, grp->connect_env);
+}
+
+
+/**
+ * Join a multicast group.
+ *
+ * The entity joining is always the local peer. Further information about the
+ * candidate can be provided in the @a join_request message. If the join fails, the
+ * @a message_cb is invoked with a (failure) response and then with NULL. If
+ * the join succeeds, outstanding (state) messages and ongoing multicast
+ * messages will be given to the @a message_cb until the member decides to part
+ * the group. The @a replay_cb function may be called at any time by the
+ * multicast service to support relaying messages to other members of the group.
+ *
+ * @param cfg
+ * Configuration to use.
+ * @param group_key
+ * ECC public key that identifies the group to join.
+ * @param member_key
+ * ECC key that identifies the member
+ * and used to sign requests sent to the origin.
+ * @param origin
+ * Peer ID of the origin to send unicast requsets to. If NULL,
+ * unicast requests are sent back via multiple hops on the reverse path
+ * of multicast messages.
+ * @param relay_count
+ * Number of peers in the @a relays array.
+ * @param relays
+ * Peer identities of members of the group, which serve as relays
+ * and can be used to join the group at. and send the @a join_request to.
+ * If empty, the @a join_request is sent directly to the @a origin.
+ * @param join_msg
+ * Application-dependent join message to be passed to the peer @a origin.
+ * @param join_request_cb
+ * Function called to approve / disapprove joining of a peer.
+ * @param join_decision_cb
+ * Function called to inform about the join decision.
+ * @param replay_frag_cb
+ * Function that can be called to replay message fragments
+ * this peer already knows from this group. NULL if this
+ * client is unable to support replay.
+ * @param replay_msg_cb
+ * Function that can be called to replay message fragments
+ * this peer already knows from this group. NULL if this
+ * client is unable to support replay.
+ * @param message_cb
+ * Function to be called for all message fragments we
+ * receive from the group, excluding those our @a replay_cb
+ * already has.
+ * @param cls
+ * Closure for callbacks.
+ *
+ * @return Handle for the member, NULL on error.
+ */
+struct GNUNET_MULTICAST_Member *
+GNUNET_MULTICAST_member_join (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *group_pub_key,
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *member_key,
+ const struct GNUNET_PeerIdentity *origin,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_msg,
+ GNUNET_MULTICAST_JoinRequestCallback join_request_cb,
+ GNUNET_MULTICAST_JoinDecisionCallback join_decision_cb,
+ GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb,
+ GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb,
+ GNUNET_MULTICAST_MessageCallback message_cb,
+ void *cls)
+{
+ struct GNUNET_MULTICAST_Member *mem = GNUNET_malloc (sizeof (*mem));
+ struct GNUNET_MULTICAST_Group *grp = &mem->grp;
+
+ uint16_t relay_size = relay_count * sizeof (*relays);
+ uint16_t join_msg_size = (NULL != join_msg) ? ntohs (join_msg->size) : 0;
+ struct MulticastMemberJoinMessage *join;
+ grp->connect_env = GNUNET_MQ_msg_extra (join, relay_size + join_msg_size,
+ GNUNET_MESSAGE_TYPE_MULTICAST_MEMBER_JOIN);
+ join->group_pub_key = *group_pub_key;
+ join->member_key = *member_key;
+ join->origin = *origin;
+ join->relay_count = ntohl (relay_count);
+ if (0 < relay_size)
+ GNUNET_memcpy (&join[1], relays, relay_size);
+ if (0 < join_msg_size)
+ GNUNET_memcpy (((char *) &join[1]) + relay_size, join_msg, join_msg_size);
+
+ grp->cfg = cfg;
+ grp->is_origin = GNUNET_NO;
+ grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+
+ mem->join_dcsn_cb = join_decision_cb;
+ grp->join_req_cb = join_request_cb;
+ grp->replay_frag_cb = replay_frag_cb;
+ grp->replay_msg_cb = replay_msg_cb;
+ grp->message_cb = message_cb;
+ grp->cb_cls = cls;
+
+ member_connect (mem);
+ return mem;
+}
+
+
+/**
+ * Part a multicast group.
+ *
+ * Disconnects from all group members and invalidates the @a member handle.
+ *
+ * An application-dependent part message can be transmitted beforehand using
+ * #GNUNET_MULTICAST_member_to_origin())
+ *
+ * @param member
+ * Membership handle.
+ */
+void
+GNUNET_MULTICAST_member_part (struct GNUNET_MULTICAST_Member *mem,
+ GNUNET_ContinuationCallback part_cb,
+ void *part_cls)
+{
+ struct GNUNET_MULTICAST_Group *grp = &mem->grp;
+ struct GNUNET_MQ_Envelope *env;
+
+ mem->join_dcsn_cb = NULL;
+ grp->join_req_cb = NULL;
+ grp->message_cb = NULL;
+ grp->replay_msg_cb = NULL;
+ grp->replay_frag_cb = NULL;
+ grp->is_disconnecting = GNUNET_YES;
+ grp->disconnect_cb = part_cb;
+ grp->disconnect_cls = part_cls;
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_PART_REQUEST);
+ GNUNET_MQ_send (grp->mq, env);
+}
+
+
+void
+member_replay_request (struct GNUNET_MULTICAST_Member *mem,
+ uint64_t fragment_id,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags)
+{
+ struct MulticastReplayRequestMessage *rep;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (rep, GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST);
+
+ rep->fragment_id = GNUNET_htonll (fragment_id);
+ rep->message_id = GNUNET_htonll (message_id);
+ rep->fragment_offset = GNUNET_htonll (fragment_offset);
+ rep->flags = GNUNET_htonll (flags);
+
+ GNUNET_MQ_send (mem->grp.mq, env);
+}
+
+
+/**
+ * Request a fragment to be replayed by fragment ID.
+ *
+ * Useful if messages below the @e max_known_fragment_id given when joining are
+ * needed and not known to the client.
+ *
+ * @param member
+ * Membership handle.
+ * @param fragment_id
+ * ID of a message fragment that this client would like to see replayed.
+ * @param flags
+ * Additional flags for the replay request.
+ * It is used and defined by GNUNET_MULTICAST_ReplayFragmentCallback
+ *
+ * @return Replay request handle.
+ */
+struct GNUNET_MULTICAST_MemberReplayHandle *
+GNUNET_MULTICAST_member_replay_fragment (struct GNUNET_MULTICAST_Member *mem,
+ uint64_t fragment_id,
+ uint64_t flags)
+{
+ member_replay_request (mem, fragment_id, 0, 0, flags);
+ // FIXME: return something useful
+ return NULL;
+}
+
+
+/**
+ * Request a message fragment to be replayed.
+ *
+ * Useful if messages below the @e max_known_fragment_id given when joining are
+ * needed and not known to the client.
+ *
+ * @param member
+ * Membership handle.
+ * @param message_id
+ * ID of the message this client would like to see replayed.
+ * @param fragment_offset
+ * Offset of the fragment within the message to replay.
+ * @param flags
+ * Additional flags for the replay request.
+ * It is used & defined by GNUNET_MULTICAST_ReplayMessageCallback
+ *
+ * @return Replay request handle, NULL on error.
+ */
+struct GNUNET_MULTICAST_MemberReplayHandle *
+GNUNET_MULTICAST_member_replay_message (struct GNUNET_MULTICAST_Member *mem,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags)
+{
+ member_replay_request (mem, 0, message_id, fragment_offset, flags);
+ // FIXME: return something useful
+ return NULL;
+}
+
+
+static void
+member_to_origin (struct GNUNET_MULTICAST_Member *mem)
+{
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "member_to_origin()\n");
+ struct GNUNET_MULTICAST_Group *grp = &mem->grp;
+ struct GNUNET_MULTICAST_MemberTransmitHandle *tmit = &mem->tmit;
+ GNUNET_assert (GNUNET_YES == grp->in_transmit);
+
+ size_t buf_size = GNUNET_MULTICAST_FRAGMENT_MAX_SIZE;
+ struct GNUNET_MULTICAST_RequestHeader *req;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (req, buf_size - sizeof(*req),
+ GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST);
+
+ int ret = tmit->notify (tmit->notify_cls, &buf_size, &req[1]);
+
+ if (! (GNUNET_YES == ret || GNUNET_NO == ret)
+ || GNUNET_MULTICAST_FRAGMENT_MAX_SIZE < buf_size)
+ {
+ LOG (GNUNET_ERROR_TYPE_ERROR,
+ "MemberTransmitNotify() returned error or invalid message size. "
+ "ret=%d, buf_size=%u\n", ret, buf_size);
+ /* FIXME: handle error */
+ GNUNET_MQ_discard (env);
+ return;
+ }
+
+ if (GNUNET_NO == ret && 0 == buf_size)
+ {
+ /* Transmission paused. */
+ GNUNET_MQ_discard (env);
+ return;
+ }
+
+ req->header.size = htons (sizeof (*req) + buf_size);
+ req->request_id = GNUNET_htonll (tmit->request_id);
+ req->fragment_offset = GNUNET_ntohll (tmit->fragment_offset);
+ tmit->fragment_offset += sizeof (*req) + buf_size;
+
+ GNUNET_MQ_send (grp->mq, env);
+
+ if (GNUNET_YES == ret)
+ grp->in_transmit = GNUNET_NO;
+}
+
+
+/**
+ * Send a message to the origin of the multicast group.
+ *
+ * @param mem
+ * Membership handle.
+ * @param request_id
+ * Application layer ID for the request. Opaque to multicast.
+ * @param notify
+ * Callback to call to get the message.
+ * @param notify_cls
+ * Closure for @a notify.
+ *
+ * @return Handle to cancel request, NULL on error (i.e. request already pending).
+ */
+struct GNUNET_MULTICAST_MemberTransmitHandle *
+GNUNET_MULTICAST_member_to_origin (struct GNUNET_MULTICAST_Member *mem,
+ uint64_t request_id,
+ GNUNET_MULTICAST_MemberTransmitNotify notify,
+ void *notify_cls)
+{
+ if (GNUNET_YES == mem->grp.in_transmit)
+ return NULL;
+ mem->grp.in_transmit = GNUNET_YES;
+
+ struct GNUNET_MULTICAST_MemberTransmitHandle *tmit = &mem->tmit;
+ tmit->member = mem;
+ tmit->request_id = request_id;
+ tmit->fragment_offset = 0;
+ tmit->notify = notify;
+ tmit->notify_cls = notify_cls;
+
+ member_to_origin (mem);
+ return tmit;
+}
+
+
+/**
+ * Resume message transmission to origin.
+ *
+ * @param th
+ * Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_member_to_origin_resume (struct GNUNET_MULTICAST_MemberTransmitHandle *th)
+{
+ struct GNUNET_MULTICAST_Group *grp = &th->member->grp;
+ if (0 != grp->acks_pending || GNUNET_YES != grp->in_transmit)
+ return;
+ member_to_origin (th->member);
+}
+
+
+/**
+ * Cancel request for message transmission to origin.
+ *
+ * @param th
+ * Transmission to cancel.
+ */
+void
+GNUNET_MULTICAST_member_to_origin_cancel (struct GNUNET_MULTICAST_MemberTransmitHandle *th)
+{
+ th->member->grp.in_transmit = GNUNET_NO;
+}
+
+
+/* end of multicast_api.c */
diff --git a/src/multicast/test_multicast.c b/src/multicast/test_multicast.c
new file mode 100644
index 0000000..70efdcb
--- /dev/null
+++ b/src/multicast/test_multicast.c
@@ -0,0 +1,758 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file multicast/test_multicast.c
+ * @brief Tests for the Multicast API.
+ * @author Gabor X Toth
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_common.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_testing_lib.h"
+#include "gnunet_multicast_service.h"
+
+#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * Return value from 'main'.
+ */
+static int res;
+
+/**
+ * Handle for task for timeout termination.
+ */
+static struct GNUNET_SCHEDULER_Task * end_badly_task;
+
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+struct GNUNET_PeerIdentity this_peer;
+
+struct GNUNET_MULTICAST_Origin *origin;
+struct GNUNET_MULTICAST_Member *member;
+
+struct GNUNET_CRYPTO_EddsaPrivateKey *group_key;
+struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key;
+
+struct GNUNET_CRYPTO_EcdsaPrivateKey *member_key;
+struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+struct TransmitClosure {
+ struct GNUNET_MULTICAST_OriginTransmitHandle *orig_tmit;
+ struct GNUNET_MULTICAST_MemberTransmitHandle *mem_tmit;
+ char * data[16];
+ uint8_t data_delay[16];
+ uint8_t data_count;
+ uint8_t paused;
+ uint8_t n;
+} tmit_cls;
+
+struct OriginClosure {
+ uint8_t msgs_expected;
+ uint8_t n;
+} origin_cls;
+
+struct MemberClosure {
+ uint8_t msgs_expected;
+ size_t n;
+} member_cls;
+
+struct GNUNET_MessageHeader *join_req, *join_resp;
+
+enum
+{
+ TEST_NONE = 0,
+ TEST_ORIGIN_START = 1,
+ TEST_MEMBER_JOIN_REFUSE = 2,
+ TEST_MEMBER_JOIN_ADMIT = 3,
+ TEST_ORIGIN_TO_ALL = 4,
+ TEST_ORIGIN_TO_ALL_RECV = 5,
+ TEST_MEMBER_TO_ORIGIN = 6,
+ TEST_MEMBER_REPLAY_ERROR = 7,
+ TEST_MEMBER_REPLAY_OK = 8,
+ TEST_MEMBER_PART = 9,
+ TEST_ORIGIN_STOP = 10,
+} test;
+
+uint64_t replay_fragment_id;
+uint64_t replay_flags;
+
+static void
+member_join (int t);
+
+
+/**
+ * Clean up all resources used.
+ */
+static void
+cleanup ()
+{
+ if (NULL != member)
+ {
+ GNUNET_MULTICAST_member_part (member, NULL, NULL);
+ member = NULL;
+ }
+ if (NULL != origin)
+ {
+ GNUNET_MULTICAST_origin_stop (origin, NULL, NULL);
+ origin = NULL;
+ }
+}
+
+
+/**
+ * Terminate the test case (failure).
+ *
+ * @param cls NULL
+ */
+static void
+end_badly (void *cls)
+{
+ res = 1;
+ cleanup ();
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Test FAILED.\n");
+}
+
+
+/**
+ * Terminate the test case (success).
+ *
+ * @param cls NULL
+ */
+static void
+end_normally (void *cls)
+{
+ res = 0;
+ cleanup ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Test PASSED.\n");
+}
+
+
+/**
+ * Finish the test case (successfully).
+ */
+static void
+end ()
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Ending tests.\n");
+
+ if (end_badly_task != NULL)
+ {
+ GNUNET_SCHEDULER_cancel (end_badly_task);
+ end_badly_task = NULL;
+ }
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS,
+ &end_normally, NULL);
+}
+
+
+static void
+tmit_resume (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmission resumed.\n");
+ struct TransmitClosure *tmit = cls;
+ if (NULL != tmit->orig_tmit)
+ GNUNET_MULTICAST_origin_to_all_resume (tmit->orig_tmit);
+ else if (NULL != tmit->mem_tmit)
+ GNUNET_MULTICAST_member_to_origin_resume (tmit->mem_tmit);
+}
+
+
+static int
+tmit_notify (void *cls, size_t *data_size, void *data)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%u: origin_tmit_notify()\n", test);
+ struct TransmitClosure *tmit = cls;
+
+ if (0 == tmit->data_count)
+ {
+ *data_size = 0;
+ return GNUNET_YES;
+ }
+
+ uint16_t size = strlen (tmit->data[tmit->n]);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Transmit notify data: %u bytes available, processing fragment %u/%u (size %u).\n",
+ (unsigned int) *data_size,
+ tmit->n + 1,
+ tmit->data_count,
+ size);
+ if (*data_size < size)
+ {
+ *data_size = 0;
+ GNUNET_assert (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_YES != tmit->paused && 0 < tmit->data_delay[tmit->n])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmission paused.\n");
+ tmit->paused = GNUNET_YES;
+ GNUNET_SCHEDULER_add_delayed (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+ tmit->data_delay[tmit->n]),
+ tmit_resume, tmit);
+ *data_size = 0;
+ return GNUNET_NO;
+ }
+ tmit->paused = GNUNET_NO;
+
+ *data_size = size;
+ GNUNET_memcpy (data, tmit->data[tmit->n], size);
+
+ return ++tmit->n < tmit->data_count ? GNUNET_NO : GNUNET_YES;
+}
+
+
+static void
+member_recv_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_recv_join_request()\n", test);
+}
+
+
+static void
+origin_stopped (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_stopped()\n", test);
+ end ();
+}
+
+
+static void
+schedule_origin_stop (void *cls)
+{
+ test = TEST_ORIGIN_STOP;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_stop()\n", test);
+ GNUNET_MULTICAST_origin_stop (origin, origin_stopped, NULL);
+ origin = NULL;
+}
+
+
+static void
+member_parted (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_parted()\n", test);
+ member = NULL;
+
+ switch (test)
+ {
+ case TEST_MEMBER_JOIN_REFUSE:
+ // Test 3 starts here
+ member_join (TEST_MEMBER_JOIN_ADMIT);
+ break;
+
+ case TEST_MEMBER_PART:
+ GNUNET_SCHEDULER_add_now (&schedule_origin_stop, NULL);
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid test #%d in member_parted()\n", test);
+ GNUNET_assert (0);
+ }
+}
+
+
+static void
+schedule_member_part (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: schedule_member_part()\n", test);
+ GNUNET_MULTICAST_member_part (member, member_parted, NULL);
+}
+
+
+static void
+member_part ()
+{
+ test = TEST_MEMBER_PART;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_part()\n", test);
+ // Test 10 starts here
+ GNUNET_SCHEDULER_add_now (&schedule_member_part, NULL);
+}
+
+
+static void
+member_replay_ok ()
+{
+ // Execution of test 8 here
+ test = TEST_MEMBER_REPLAY_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_replay_ok()\n", test);
+ replay_fragment_id = 1;
+ replay_flags = 1 | 1<<11;
+ GNUNET_MULTICAST_member_replay_fragment (member, replay_fragment_id,
+ replay_flags);
+}
+
+
+static void
+member_replay_error ()
+{
+ test = TEST_MEMBER_REPLAY_ERROR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_replay_error()\n", test);
+ replay_fragment_id = 1234;
+ replay_flags = 11 | 1<<11;
+ GNUNET_MULTICAST_member_replay_fragment (member, replay_fragment_id,
+ replay_flags);
+}
+
+
+static void
+origin_recv_replay_msg (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_recv_replay_msg()\n", test);
+ GNUNET_assert (0);
+}
+
+
+static void
+member_recv_replay_msg (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_recv_replay_msg()\n", test);
+ GNUNET_assert (0);
+}
+
+
+static void
+origin_recv_replay_frag (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key,
+ uint64_t fragment_id,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_recv_replay_frag()"
+ " - fragment_id=%" PRIu64 " flags=%" PRIu64 "\n",
+ test, fragment_id, flags);
+ GNUNET_assert (replay_fragment_id == fragment_id && replay_flags == flags);
+ switch (test)
+ {
+ case TEST_MEMBER_REPLAY_ERROR:
+ // Test 8 starts here
+ GNUNET_MULTICAST_replay_response (rh, NULL, GNUNET_SYSERR);
+ member_replay_ok ();
+ break;
+
+ case TEST_MEMBER_REPLAY_OK:
+ {
+ struct GNUNET_MULTICAST_MessageHeader mmsg = {
+ .header = {
+ .type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE),
+ .size = htons (sizeof (mmsg)),
+ },
+ .fragment_id = GNUNET_htonll (1),
+ .message_id = GNUNET_htonll (1),
+ .fragment_offset = 0,
+ .group_generation = GNUNET_htonll (1),
+ .flags = 0,
+ };
+ member_cls.n = 0;
+ member_cls.msgs_expected = 1;
+ GNUNET_MULTICAST_replay_response (rh, &mmsg.header, GNUNET_MULTICAST_REC_OK);
+ GNUNET_MULTICAST_replay_response_end (rh);
+ break;
+ }
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid test #%d in origin_recv_replay_frag()\n", test);
+ GNUNET_assert (0);
+ }
+}
+
+
+static void
+member_recv_replay_frag (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key,
+ uint64_t fragment_id,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_recv_replay_frag()\n", test);
+ GNUNET_assert (0);
+}
+
+
+static void
+origin_recv_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ struct OriginClosure *ocls = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_recv_request()\n", test);
+ if (++ocls->n != ocls->msgs_expected)
+ return;
+
+ GNUNET_assert (0 == memcmp (&req->member_pub_key,
+ &member_pub_key, sizeof (member_pub_key)));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%u: verify message content, take first 3 bytes: %.3s\n",
+ test, (char *)&req[1]);
+ GNUNET_assert (0 == memcmp (&req[1], "abc", 3));
+
+ // Test 7 starts here
+ member_replay_error ();
+}
+
+
+static void
+member_to_origin ()
+{
+ test = TEST_MEMBER_TO_ORIGIN;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_to_origin()\n", test);
+
+ struct TransmitClosure *tmit = &tmit_cls;
+ *tmit = (struct TransmitClosure) {};
+ tmit->data[0] = "abc def";
+ tmit->data[1] = "ghi jkl mno";
+ tmit->data_delay[1] = 2;
+ tmit->data[2] = "pqr stuw xyz";
+ tmit->data_count = 3;
+
+ origin_cls.n = 0;
+ origin_cls.msgs_expected = 1;
+
+ tmit->mem_tmit = GNUNET_MULTICAST_member_to_origin (member, 1,
+ tmit_notify, tmit);
+}
+
+
+static void
+member_recv_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ struct MemberClosure *mcls = cls;
+
+ // Test 5 starts here after message has been received from origin
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%u: member_recv_message() %u/%u\n",
+ test,
+ (unsigned int) (mcls->n + 1),
+ mcls->msgs_expected);
+ if (++mcls->n != mcls->msgs_expected)
+ return;
+
+ // FIXME: check message content
+
+ switch (test)
+ {
+ case TEST_ORIGIN_TO_ALL:
+ test = TEST_ORIGIN_TO_ALL_RECV;
+ break;
+
+ case TEST_ORIGIN_TO_ALL_RECV:
+ // Test 6 starts here
+ member_to_origin ();
+ break;
+
+ case TEST_MEMBER_REPLAY_OK:
+ // Test 9 starts here
+ GNUNET_assert (replay_fragment_id == GNUNET_ntohll (msg->fragment_id));
+ member_part ();
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid test #%d in origin_recv_message()\n", test);
+ GNUNET_assert (0);
+ }
+}
+
+
+static void
+origin_recv_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ struct OriginClosure *ocls = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_recv_message() %u/%u\n",
+ test, ocls->n + 1, ocls->msgs_expected);
+ if (++ocls->n != ocls->msgs_expected)
+ return;
+
+ // FIXME: check message content
+
+ switch (test)
+ {
+ case TEST_ORIGIN_TO_ALL:
+ // Prepare to execute test 5
+ test = TEST_ORIGIN_TO_ALL_RECV;
+ break;
+
+ case TEST_ORIGIN_TO_ALL_RECV:
+ // Test 6 starts here
+ member_to_origin ();
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid test #%d in origin_recv_message()\n", test);
+ GNUNET_assert (0);
+ }
+}
+
+
+static void
+origin_to_all ()
+{
+ test = TEST_ORIGIN_TO_ALL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_to_all()\n", test);
+
+ struct TransmitClosure *tmit = &tmit_cls;
+ *tmit = (struct TransmitClosure) {};
+ tmit->data[0] = "ABC DEF";
+ tmit->data[1] = GNUNET_malloc (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD + 1);
+ uint16_t i;
+ for (i = 0; i < GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD; i++)
+ tmit->data[1][i] = (0 == i % 10000) ? '0' + i / 10000 : '_';
+ tmit->data[2] = "GHI JKL MNO";
+ tmit->data_delay[2] = 2;
+ tmit->data[3] = "PQR STUW XYZ";
+ tmit->data_count = 4;
+
+ origin_cls.n = member_cls.n = 0;
+ origin_cls.msgs_expected = member_cls.msgs_expected = tmit->data_count;
+
+ tmit->orig_tmit = GNUNET_MULTICAST_origin_to_all (origin, 1, 1,
+ tmit_notify, tmit);
+}
+
+
+static void
+member_recv_join_decision (void *cls,
+ int is_admitted,
+ const struct GNUNET_PeerIdentity *peer,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_recv_join_decision() - is_admitted: %d\n",
+ test, is_admitted);
+
+ GNUNET_assert (join_msg->size == join_resp->size);
+ GNUNET_assert (join_msg->type == join_resp->type);
+ GNUNET_assert (0 == memcmp (join_msg, join_resp, ntohs (join_resp->size)));
+
+ switch (test)
+ {
+ case TEST_MEMBER_JOIN_REFUSE:
+ GNUNET_assert (0 == relay_count);
+ // Test 3 starts here
+ GNUNET_SCHEDULER_add_now (&schedule_member_part, NULL);
+ break;
+
+ case TEST_MEMBER_JOIN_ADMIT:
+ GNUNET_assert (1 == relay_count);
+ GNUNET_assert (0 == memcmp (relays, &this_peer, sizeof (this_peer)));
+ // Test 4 starts here
+ origin_to_all ();
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid test #%d in member_recv_join_decision()\n", test);
+ GNUNET_assert (0);
+ }
+}
+
+/**
+ * Test: origin receives join request
+ */
+static void
+origin_recv_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *mem_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_recv_join_request()\n", test);
+
+ GNUNET_assert (0 == memcmp (mem_key, &member_pub_key, sizeof (member_pub_key)));
+ GNUNET_assert (join_msg->size == join_req->size);
+ GNUNET_assert (join_msg->type == join_req->type);
+ GNUNET_assert (0 == memcmp (join_msg, join_req, ntohs (join_req->size)));
+
+ char data[] = "here's the decision";
+ uint8_t data_size = strlen (data) + 1;
+ join_resp = GNUNET_malloc (sizeof (join_resp) + data_size);
+ join_resp->size = htons (sizeof (join_resp) + data_size);
+ join_resp->type = htons (456);
+ GNUNET_memcpy (&join_resp[1], data, data_size);
+
+ switch (test)
+ {
+ case TEST_MEMBER_JOIN_REFUSE:
+ // Test 3 starts here
+ GNUNET_MULTICAST_join_decision (jh, GNUNET_NO, 0, NULL, join_resp);
+ break;
+
+ case TEST_MEMBER_JOIN_ADMIT:
+ // Test 3 is running
+ GNUNET_MULTICAST_join_decision (jh, GNUNET_YES, 1, &this_peer, join_resp);
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid test #%d in origin_recv_join_request()\n", test);
+ GNUNET_assert (0);
+ break;
+ }
+}
+
+/**
+ * Test: member joins multicast group
+ */
+static void
+member_join (int t)
+{
+ test = t;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: member_join()\n", test);
+
+ member_key = GNUNET_CRYPTO_ecdsa_key_create ();
+ GNUNET_CRYPTO_ecdsa_key_get_public (member_key, &member_pub_key);
+
+ if (NULL != join_req)
+ GNUNET_free (join_req);
+
+ char data[] = "let me in!";
+ uint8_t data_size = strlen (data) + 1;
+ join_req = GNUNET_malloc (sizeof (join_req) + data_size);
+ join_req->size = htons (sizeof (join_req) + data_size);
+ join_req->type = htons (123);
+ GNUNET_memcpy (&join_req[1], data, data_size);
+
+ member = GNUNET_MULTICAST_member_join (cfg, &group_pub_key, member_key,
+ &this_peer, 1, &this_peer, join_req,
+ member_recv_join_request,
+ member_recv_join_decision,
+ member_recv_replay_frag,
+ member_recv_replay_msg,
+ member_recv_message,
+ &member_cls);
+}
+
+/**
+ * Test: Start a multicast group as origin
+ */
+static void
+origin_start ()
+{
+ test = TEST_ORIGIN_START;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%u: origin_start()\n", test);
+
+ group_key = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_CRYPTO_eddsa_key_get_public (group_key, &group_pub_key);
+
+ origin = GNUNET_MULTICAST_origin_start (cfg, group_key, 0,
+ origin_recv_join_request,
+ origin_recv_replay_frag,
+ origin_recv_replay_msg,
+ origin_recv_request,
+ origin_recv_message,
+ &origin_cls);
+ // Test 2 starts here
+ member_join (TEST_MEMBER_JOIN_REFUSE);
+}
+
+
+/**
+ * Main function of the test, run from scheduler.
+ *
+ * @param cls NULL
+ * @param cfg configuration we use (also to connect to Multicast service)
+ * @param peer handle to access more of the peer (not used)
+ */
+static void
+#if DEBUG_TEST_MULTICAST
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+#else
+run (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *c,
+ struct GNUNET_TESTING_Peer *peer)
+#endif
+{
+ cfg = c;
+ end_badly_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
+ &end_badly, NULL);
+ GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer);
+
+ // Test 1 starts here
+ origin_start ();
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ res = 1;
+#if DEBUG_TEST_MULTICAST
+ const struct GNUNET_GETOPT_CommandLineOption opts[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+ if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "test-multicast",
+ "test-multicast [options]",
+ opts, &run, NULL))
+ return 1;
+#else
+ if (0 != GNUNET_TESTING_peer_run ("test-multicast", "test_multicast.conf", &run, NULL))
+ return 1;
+#endif
+ return res;
+}
+
+/* end of test_multicast.c */
diff --git a/src/multicast/test_multicast.conf b/src/multicast/test_multicast.conf
new file mode 100644
index 0000000..b2f1a76
--- /dev/null
+++ b/src/multicast/test_multicast.conf
@@ -0,0 +1,56 @@
+[testbed]
+HOSTNAME = localhost
+
+[arm]
+GLOBAL_POSTFIX=-L ERROR
+
+[multicast]
+#PREFIX = tmux new-window gdb -x ./cmd.gdb --args
+#PREFIX = valgrind --leak-check=full
+UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock
+
+[vpn]
+START_ON_DEMAND = NO
+
+[peerinfo]
+# Do not use shipped gnunet HELLOs
+USE_INCLUDED_HELLOS = NO
+
+# Option to disable all disk IO; only useful for testbed runs
+# (large-scale experiments); disables persistence of HELLOs!
+NO_IO = YES
+
+[hostlist]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[nat]
+ENABLE_UPNP = NO
+
+[fs]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[vpn]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[revocation]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[gns]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[namestore]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[namecache]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[topology]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
diff --git a/src/multicast/test_multicast_2peers.c b/src/multicast/test_multicast_2peers.c
new file mode 100644
index 0000000..ea99602
--- /dev/null
+++ b/src/multicast/test_multicast_2peers.c
@@ -0,0 +1,520 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file multicast/test_multicast_2peers.c
+ * @brief Tests for the Multicast API with two peers doing the ping
+ * pong test.
+ * @author xrs
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_common.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_testbed_service.h"
+#include "gnunet_multicast_service.h"
+
+#define NUM_PEERS 2
+
+static struct GNUNET_TESTBED_Operation *op0;
+static struct GNUNET_TESTBED_Operation *op1;
+static struct GNUNET_TESTBED_Operation *pi_op0;
+static struct GNUNET_TESTBED_Operation *pi_op1;
+
+static struct GNUNET_TESTBED_Peer **peers;
+const struct GNUNET_PeerIdentity *peer_id[2];
+
+static struct GNUNET_SCHEDULER_Task *timeout_tid;
+
+static struct GNUNET_MULTICAST_Origin *origin;
+static struct GNUNET_MULTICAST_Member *member;
+
+struct GNUNET_CRYPTO_EddsaPrivateKey *group_key;
+struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key;
+
+struct GNUNET_CRYPTO_EcdsaPrivateKey *member_key;
+struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key;
+
+/**
+ * Global result for testcase.
+ */
+static int result;
+
+
+/**
+ * Function run on CTRL-C or shutdown (i.e. success/timeout/etc.).
+ * Cleans up.
+ */
+static void
+shutdown_task (void *cls)
+{
+ if (NULL != op0)
+ {
+ GNUNET_TESTBED_operation_done (op0);
+ op0 = NULL;
+ }
+ if (NULL != op1)
+ {
+ GNUNET_TESTBED_operation_done (op1);
+ op1 = NULL;
+ }
+ if (NULL != pi_op0)
+ {
+ GNUNET_TESTBED_operation_done (pi_op0);
+ pi_op0 = NULL;
+ }
+ if (NULL != pi_op1)
+ {
+ GNUNET_TESTBED_operation_done (pi_op1);
+ pi_op1 = NULL;
+ }
+ if (NULL != timeout_tid)
+ {
+ GNUNET_SCHEDULER_cancel (timeout_tid);
+ timeout_tid = NULL;
+ }
+}
+
+
+static void
+timeout_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Timeout!\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+member_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Member sent a join request.\n");
+
+}
+
+
+static int
+notify (void *cls,
+ size_t *data_size,
+ void *data)
+{
+
+ char text[] = "ping";
+ *data_size = strlen(text)+1;
+ GNUNET_memcpy(data, text, *data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Member sents message to origin: %s\n", text);
+
+ return GNUNET_YES;
+}
+
+
+static void
+member_join_decision (void *cls,
+ int is_admitted,
+ const struct GNUNET_PeerIdentity *peer,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Member received a decision from origin: %s\n",
+ (GNUNET_YES == is_admitted)
+ ? "accepted"
+ : "rejected");
+
+ if (GNUNET_YES == is_admitted)
+ {
+ struct GNUNET_MULTICAST_MemberTransmitHandle *req;
+
+ // FIXME: move to MQ-style API!
+ req = GNUNET_MULTICAST_member_to_origin (member,
+ 0,
+ &notify,
+ NULL);
+ }
+}
+
+
+static void
+member_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ if (0 != strncmp ("pong", (char *)&msg[1], 4))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "member did not receive pong\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "member receives: %s\n", (char *)&msg[1]);
+
+ // Testcase ends here.
+ result = GNUNET_YES;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+origin_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ struct GNUNET_MessageHeader *join_resp;
+
+ uint8_t data_size = ntohs (join_msg->size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "origin got a join request...\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "origin receives: '%s'\n", (char *)&join_msg[1]);
+
+ const char data[] = "Come in!";
+ data_size = strlen (data) + 1;
+ join_resp = GNUNET_malloc (sizeof (join_resp) + data_size);
+ join_resp->size = htons (sizeof (join_resp) + data_size);
+ join_resp->type = htons (123);
+ GNUNET_memcpy (&join_resp[1], data, data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "origin sends: '%s'\n", data);
+
+ GNUNET_MULTICAST_join_decision (jh,
+ GNUNET_YES,
+ 0,
+ NULL,
+ join_resp);
+ GNUNET_free (join_resp);
+ result = GNUNET_OK;
+}
+
+
+int
+origin_notify (void *cls,
+ size_t *data_size,
+ void *data)
+{
+ char text[] = "pong";
+
+ *data_size = strlen(text)+1;
+ GNUNET_memcpy (data,
+ text,
+ *data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin sends (to all): %s\n", text);
+
+ return GNUNET_YES;
+}
+
+
+static void
+origin_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin receives: %s\n", (char *)&req[1]);
+
+ if (0 != strncmp ("ping", (char *)&req[1], 4))
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "origin didn't reveice a correct request");
+
+ GNUNET_MULTICAST_origin_to_all (origin,
+ 0,
+ 0,
+ origin_notify,
+ NULL);
+}
+
+
+static void
+origin_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin message msg\n");
+}
+
+
+static void
+service_connect1 (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ void *ca_result,
+ const char *emsg)
+{
+ member = ca_result;
+
+ if (NULL == member)
+ {
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to multicast service of member\n");
+ }
+}
+
+
+static void
+multicast_da1 (void *cls,
+ void * op_result)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Member parting from multicast group\n");
+
+ GNUNET_MULTICAST_member_part (member, NULL, NULL);
+}
+
+
+static void *
+multicast_ca1 (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_MessageHeader *join_msg;
+ void *ret;
+
+ // Get members keys
+ member_key = GNUNET_CRYPTO_ecdsa_key_create ();
+ GNUNET_CRYPTO_ecdsa_key_get_public (member_key, &member_pub_key);
+
+ char data[] = "Hi, can I enter?";
+ uint8_t data_size = strlen (data) + 1;
+ join_msg = GNUNET_malloc (sizeof (join_msg) + data_size);
+ join_msg->size = htons (sizeof (join_msg) + data_size);
+ join_msg->type = htons (123);
+ GNUNET_memcpy (&join_msg[1], data, data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Members tries to join multicast group\n");
+
+ ret = GNUNET_MULTICAST_member_join (cfg,
+ &group_pub_key,
+ member_key,
+ peer_id[0],
+ 0,
+ NULL,
+ join_msg, /* join message */
+ member_join_request,
+ member_join_decision,
+ NULL, /* no test for member_replay_frag */
+ NULL, /* no test for member_replay_msg */
+ member_message,
+ NULL);
+ GNUNET_free (join_msg);
+ return ret;
+}
+
+
+static void
+peer_information_cb (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ const struct GNUNET_TESTBED_PeerInformation *pinfo,
+ const char *emsg)
+{
+ int i = (int) (long) cls;
+
+ if (NULL == pinfo)
+ {
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+
+ peer_id[i] = pinfo->result.id;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got peer information of %s (%s)\n", (0==i)?"origin":"member" ,GNUNET_i2s(pinfo->result.id));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Create member peer\n");
+
+ if (0 == i)
+ {
+ /* connect to multicast service of member */
+ op1 = GNUNET_TESTBED_service_connect (NULL, /* Closure for operation */
+ peers[1], /* The peer whose service to connect to */
+ "multicast", /* The name of the service */
+ service_connect1, /* callback to call after a handle to service
+ is opened */
+ NULL, /* closure for the above callback */
+ multicast_ca1, /* callback to call with peer's configuration;
+ this should open the needed service connection */
+ multicast_da1, /* callback to be called when closing the
+ opened service connection */
+ NULL); /* closure for the above two callbacks */
+ }
+}
+
+
+/**
+ * Test logic of peer "0" being origin starts here.
+ *
+ * @param cls closure, for the example: NULL
+ * @param op should be equal to "dht_op"
+ * @param ca_result result of the connect operation, the
+ * connection to the DHT service
+ * @param emsg error message, if testbed somehow failed to
+ * connect to the DHT.
+ */
+static void
+service_connect0 (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ void *ca_result,
+ const char *emsg)
+{
+ origin = ca_result;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Connected to multicast service of origin\n");
+
+ // Get GNUnet identity of origin
+ pi_op0 = GNUNET_TESTBED_peer_get_information (peers[0],
+ GNUNET_TESTBED_PIT_IDENTITY,
+ peer_information_cb,
+ (void *) 0);
+ // Get GNUnet identity of member
+ pi_op1 = GNUNET_TESTBED_peer_get_information (peers[1],
+ GNUNET_TESTBED_PIT_IDENTITY,
+ peer_information_cb,
+ (void *) 1);
+
+ /* Connection to service successful. Here we'd usually do something with
+ * the service. */
+ result = GNUNET_OK;
+ //GNUNET_SCHEDULER_shutdown (); /* Also kills the testbed */
+}
+
+
+
+/**
+ * Function run when service multicast has started and is providing us
+ * with a configuration file.
+ */
+static void *
+multicast_ca0 (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ group_key = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_CRYPTO_eddsa_key_get_public (group_key, &group_pub_key);
+
+ return GNUNET_MULTICAST_origin_start (cfg,
+ group_key,
+ 0,
+ origin_join_request,
+ NULL, /* no test for origin_replay_frag */
+ NULL, /* no test for origin_replay_msg */
+ origin_request,
+ origin_message,
+ NULL);
+}
+
+static void
+multicast_da0 (void *cls,
+ void *op_result)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Origin closes multicast group\n");
+
+ GNUNET_MULTICAST_origin_stop (origin, NULL, NULL);
+}
+
+
+/**
+ * Main function inovked from TESTBED once all of the
+ * peers are up and running. This one then connects
+ * just to the multicast service of peer 0 and 1.
+ * Peer 0 is going to be origin.
+ * Peer 1 is going to be one member.
+ * Origin will start a multicast group and the member will try to join it.
+ * After that we execute some multicast test.
+ *
+ * @param cls closure
+ * @param h the run handle
+ * @param peers started peers for the test
+ * @param num_peers size of the 'peers' array
+ * @param links_succeeded number of links between peers that were created
+ * @param links_failed number of links testbed was unable to establish
+ */
+static void
+testbed_master (void *cls,
+ struct GNUNET_TESTBED_RunHandle *h,
+ unsigned int num_peers,
+ struct GNUNET_TESTBED_Peer **p,
+ unsigned int links_succeeded,
+ unsigned int links_failed)
+{
+ /* Testbed is ready with peers running and connected in a pre-defined overlay
+ topology (FIXME) */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Connected to testbed_master()\n");
+
+ peers = p;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Create origin peer\n");
+ op0 = GNUNET_TESTBED_service_connect (NULL, /* Closure for operation */
+ peers[0], /* The peer whose service to connect to */
+ "multicast", /* The name of the service */
+ service_connect0, /* callback to call after a handle to service
+ is opened */
+ NULL, /* closure for the above callback */
+ multicast_ca0, /* callback to call with peer's configuration;
+ this should open the needed service connection */
+ multicast_da0, /* callback to be called when closing the
+ opened service connection */
+ NULL); /* closure for the above two callbacks */
+
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); /* Schedule a new task on shutdown */
+
+ /* Schedule the shutdown task with a delay of a few Seconds */
+ timeout_tid = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 50),
+ &timeout_task, NULL);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ int ret;
+
+ result = GNUNET_SYSERR;
+ ret = GNUNET_TESTBED_test_run
+ ("test-multicast-2peers", /* test case name */
+ "test_multicast.conf", /* template configuration */
+ NUM_PEERS, /* number of peers to start */
+ 0LL, /* Event mask - set to 0 for no event notifications */
+ NULL, /* Controller event callback */
+ NULL, /* Closure for controller event callback */
+ testbed_master, /* continuation callback to be called when testbed setup is complete */
+ NULL); /* Closure for the test_master callback */
+ if ( (GNUNET_OK != ret) || (GNUNET_OK != result) )
+ return 1;
+ return 0;
+}
+
+
+/* end of test_multicast_2peers.c */
diff --git a/src/multicast/test_multicast_line.conf b/src/multicast/test_multicast_line.conf
new file mode 100644
index 0000000..c1ce7c6
--- /dev/null
+++ b/src/multicast/test_multicast_line.conf
@@ -0,0 +1,63 @@
+[testbed]
+HOSTNAME = localhost
+OVERLAY_TOPOLOGY = LINE
+
+[arm]
+GLOBAL_POSTFIX=-L ERROR
+
+[multicast]
+#PREFIX = tmux new-window gdb -x ./cmd.gdb --args
+#PREFIX = valgrind --leak-check=full
+UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock
+
+[vpn]
+START_ON_DEMAND = NO
+
+[peerinfo]
+# Do not use shipped gnunet HELLOs
+USE_INCLUDED_HELLOS = NO
+
+# Option to disable all disk IO; only useful for testbed runs
+# (large-scale experiments); disables persistence of HELLOs!
+NO_IO = YES
+
+[cadet]
+ID_ANNOUNCE_TIME = 5 s
+
+[hostlist]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[nat]
+ENABLE_UPNP = NO
+
+[fs]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[vpn]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[revocation]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[gns]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[namestore]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[namecache]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[topology]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[nse]
+WORKBITS = 0
diff --git a/src/multicast/test_multicast_multipeer.c b/src/multicast/test_multicast_multipeer.c
new file mode 100644
index 0000000..9b44e05
--- /dev/null
+++ b/src/multicast/test_multicast_multipeer.c
@@ -0,0 +1,643 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file multicast/test_multicast_multipeers.c
+ * @brief Tests for the Multicast API with multiple peers.
+ * @author xrs
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_common.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_testbed_service.h"
+#include "gnunet_multicast_service.h"
+
+#define PEERS_REQUESTED 12
+
+struct MulticastPeerContext
+{
+ int peer; /* peer number */
+ struct GNUNET_CRYPTO_EcdsaPrivateKey *key;
+ const struct GNUNET_PeerIdentity *id;
+ struct GNUNET_TESTBED_Operation *op; /* not yet in use */
+ struct GNUNET_TESTBED_Operation *pi_op; /* not yet in use */
+ int test_ok;
+};
+
+enum pingpong
+{
+ PING = 1,
+ PONG = 2
+};
+
+struct pingpong_msg
+{
+ int peer;
+ enum pingpong msg;
+};
+
+static void service_connect (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ void *ca_result,
+ const char *emsg);
+
+static struct MulticastPeerContext **multicast_peers;
+static struct GNUNET_TESTBED_Peer **peers;
+
+static struct GNUNET_TESTBED_Operation *op[PEERS_REQUESTED];
+static struct GNUNET_TESTBED_Operation *pi_op[PEERS_REQUESTED];
+
+static struct GNUNET_MULTICAST_Origin *origin;
+static struct GNUNET_MULTICAST_Member *members[PEERS_REQUESTED]; /* first element always empty */
+
+static struct GNUNET_SCHEDULER_Task *timeout_tid;
+
+static struct GNUNET_CRYPTO_EddsaPrivateKey *group_key;
+static struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key;
+static struct GNUNET_HashCode group_pub_key_hash;
+
+/**
+ * Global result for testcase.
+ */
+static int result;
+
+/**
+ * Function run on CTRL-C or shutdown (i.e. success/timeout/etc.).
+ * Cleans up.
+ */
+static void
+shutdown_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "shutdown_task!\n");
+ for (int i=0;i<PEERS_REQUESTED;i++)
+ {
+ if (NULL != op[i])
+ {
+ GNUNET_TESTBED_operation_done(op[i]);
+ op[i] = NULL;
+ }
+ if (NULL != pi_op[i])
+ {
+ GNUNET_TESTBED_operation_done (pi_op[i]);
+ pi_op[i] = NULL;
+ }
+ }
+
+ if (NULL != multicast_peers)
+ {
+ for (int i=0; i < PEERS_REQUESTED; i++)
+ {
+ GNUNET_free_non_null (multicast_peers[i]->key);
+ GNUNET_free (multicast_peers[i]);
+ multicast_peers[i] = NULL;
+ }
+ GNUNET_free (multicast_peers);
+ multicast_peers = NULL;
+ }
+
+ if (NULL != timeout_tid)
+ {
+ GNUNET_SCHEDULER_cancel (timeout_tid);
+ timeout_tid = NULL;
+ }
+}
+
+
+static void
+timeout_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Timeout!\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+member_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Peer #%u (%s) sent a join request.\n",
+ mc_peer->peer,
+ GNUNET_i2s (multicast_peers[mc_peer->peer]->id));
+}
+
+
+static int
+notify (void *cls,
+ size_t *data_size,
+ void *data)
+{
+ struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls;
+
+ struct pingpong_msg *pp_msg = GNUNET_new (struct pingpong_msg);
+ pp_msg->peer = mc_peer->peer;
+ pp_msg->msg = PING;
+
+ *data_size = sizeof (struct pingpong_msg);
+ GNUNET_memcpy(data, pp_msg, *data_size);
+ GNUNET_free (pp_msg);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Peer #%u sents ping to origin\n", mc_peer->peer);
+
+ return GNUNET_YES;
+}
+
+
+static void
+member_join_decision (void *cls,
+ int is_admitted,
+ const struct GNUNET_PeerIdentity *peer,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_msg)
+{
+ struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Peer #%u (%s) received a decision from origin: %s\n",
+ mc_peer->peer,
+ GNUNET_i2s (multicast_peers[mc_peer->peer]->id),
+ (GNUNET_YES == is_admitted)?"accepted":"rejected");
+
+ if (GNUNET_YES == is_admitted)
+ {
+ GNUNET_MULTICAST_member_to_origin (members[mc_peer->peer],
+ 0,
+ notify,
+ cls);
+
+ }
+}
+
+
+static void
+member_replay_frag ()
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "member replay frag...\n");
+}
+
+
+static void
+member_replay_msg ()
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "member replay msg...\n");
+}
+
+
+static void
+origin_disconnected_cb (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Origin disconnected. Shutting down.\n");
+ result = GNUNET_YES;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+member_disconnected_cb (void *cls)
+{
+ for (int i = 1; i < PEERS_REQUESTED; ++i)
+ if (GNUNET_NO == multicast_peers[i]->test_ok)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "All member disconnected. Stopping origin.\n");
+ GNUNET_MULTICAST_origin_stop (origin, origin_disconnected_cb, cls);
+}
+
+
+static void
+member_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls;
+ struct pingpong_msg *pp_msg = (struct pingpong_msg*) &(msg[1]);
+
+ if (PONG == pp_msg->msg && mc_peer->peer == pp_msg->peer)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "peer #%i (%s) receives a pong\n",
+ mc_peer->peer,
+ GNUNET_i2s (multicast_peers[mc_peer->peer]->id));
+ mc_peer->test_ok = GNUNET_OK;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "peer #%u (%s) parting from multicast group\n",
+ mc_peer->peer,
+ GNUNET_i2s (multicast_peers[mc_peer->peer]->id));
+
+ GNUNET_MULTICAST_member_part (members[mc_peer->peer], member_disconnected_cb, cls);
+ }
+}
+
+
+static void
+origin_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ struct GNUNET_MessageHeader *join_resp;
+
+ uint8_t data_size = ntohs (join_msg->size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "origin got a join request...\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "origin receives: '%s'\n", (char *)&join_msg[1]);
+
+ char data[] = "Come in!";
+ data_size = strlen (data) + 1;
+ join_resp = GNUNET_malloc (sizeof (join_resp) + data_size);
+ join_resp->size = htons (sizeof (join_resp) + data_size);
+ join_resp->type = htons (123);
+ GNUNET_memcpy (&join_resp[1], data, data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "origin sends: '%s'\n", data);
+
+ GNUNET_MULTICAST_join_decision (jh,
+ GNUNET_YES,
+ 0,
+ NULL,
+ join_resp);
+
+ result = GNUNET_OK;
+}
+
+
+static void
+origin_replay_frag (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ uint64_t fragment_id,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin replay fraq msg\n");
+}
+
+
+static void
+origin_replay_msg (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin replay msg\n");
+}
+
+
+static int
+origin_notify (void *cls,
+ size_t *data_size,
+ void *data)
+{
+ struct pingpong_msg *rcv_pp_msg = (struct pingpong_msg*)cls;
+ struct pingpong_msg *pp_msg = GNUNET_new (struct pingpong_msg);
+
+ pp_msg->peer = rcv_pp_msg->peer;
+ pp_msg->msg = PONG;
+ *data_size = sizeof (struct pingpong_msg);
+ GNUNET_memcpy(data, pp_msg, *data_size);
+ GNUNET_free (pp_msg);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin sends pong\n");
+
+ return GNUNET_YES;
+}
+
+
+static void
+origin_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin receives a msg\n");
+
+ req++;
+ struct pingpong_msg *pp_msg = (struct pingpong_msg *) req;
+
+ if (1 != pp_msg->msg) {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "origin didn't reveice a correct request");
+ }
+
+ GNUNET_MULTICAST_origin_to_all (origin,
+ 0,
+ 0,
+ origin_notify,
+ pp_msg);
+}
+
+
+static void
+origin_message (void *cls,
+ const struct GNUNET_MULTICAST_MessageHeader *msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin message msg\n");
+}
+
+
+static void
+multicast_disconnect (void *cls,
+ void *op_result)
+{
+
+}
+
+
+static void *
+multicast_connect (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct MulticastPeerContext *multicast_peer = cls;
+ struct GNUNET_MessageHeader *join_msg;
+ char data[64];
+
+ if (0 == multicast_peer->peer)
+ {
+ group_key = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_CRYPTO_eddsa_key_get_public (group_key, &group_pub_key);
+
+ GNUNET_CRYPTO_hash (&group_pub_key, sizeof (group_pub_key), &group_pub_key_hash);
+ origin = GNUNET_MULTICAST_origin_start (cfg,
+ group_key,
+ 0,
+ origin_join_request,
+ origin_replay_frag,
+ origin_replay_msg,
+ origin_request,
+ origin_message,
+ cls);
+ if (NULL == origin)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Peer #%u could not create a multicast group",
+ multicast_peer->peer);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Peer #%u connected as origin to group %s\n",
+ multicast_peer->peer,
+ GNUNET_h2s (&group_pub_key_hash));
+ return origin;
+ }
+ else
+ {
+ multicast_peer->key = GNUNET_CRYPTO_ecdsa_key_create ();
+
+ sprintf(data, "Hi, I am peer #%u (%s). Can I enter?",
+ multicast_peer->peer,
+ GNUNET_i2s (multicast_peers[multicast_peer->peer]->id));
+ uint8_t data_size = strlen (data) + 1;
+ join_msg = GNUNET_malloc (sizeof (join_msg) + data_size);
+ join_msg->size = htons (sizeof (join_msg) + data_size);
+ join_msg->type = htons (123);
+ GNUNET_memcpy (&join_msg[1], data, data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Peer #%u (%s) tries to join multicast group %s\n",
+ multicast_peer->peer,
+ GNUNET_i2s (multicast_peers[multicast_peer->peer]->id),
+ GNUNET_h2s (&group_pub_key_hash));
+
+ members[multicast_peer->peer] =
+ GNUNET_MULTICAST_member_join (cfg,
+ &group_pub_key,
+ multicast_peer->key,
+ multicast_peers[0]->id,
+ 0,
+ NULL,
+ join_msg, /* join message */
+ member_join_request,
+ member_join_decision,
+ member_replay_frag,
+ member_replay_msg,
+ member_message,
+ cls);
+ return members[multicast_peer->peer];
+ }
+}
+
+
+static void
+peer_information_cb (void *cls,
+ struct GNUNET_TESTBED_Operation *operation,
+ const struct GNUNET_TESTBED_PeerInformation *pinfo,
+ const char *emsg)
+{
+ struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls;
+
+ if (NULL == pinfo) {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "got no peer information\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+
+ multicast_peers[mc_peer->peer]->id = pinfo->result.id;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got peer information of %s (%s)\n",
+ (0 == mc_peer->peer)? "origin" : "member",
+ GNUNET_i2s (pinfo->result.id));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Create peer #%u (%s)\n",
+ mc_peer->peer,
+ GNUNET_i2s (multicast_peers[mc_peer->peer]->id));
+
+ if (0 != mc_peer->peer)
+ {
+ /* connect to multicast service of members */
+ op[mc_peer->peer] =
+ GNUNET_TESTBED_service_connect (/* Closure for operation */
+ NULL,
+ /* The peer whose service to connect to */
+ peers[mc_peer->peer],
+ /* The name of the service */
+ "multicast",
+ /* called after a handle to service is opened */
+ service_connect,
+ /* closure for the above callback */
+ cls,
+ /* called when opening the service connection */
+ multicast_connect,
+ /* called when closing the service connection */
+ multicast_disconnect,
+ /* closure for the above two callbacks */
+ cls);
+ }
+}
+
+
+static void
+service_connect (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ void *ca_result,
+ const char *emsg)
+{
+ struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls;
+
+ if (NULL == ca_result)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Connection adapter not created for peer #%u (%s)\n",
+ mc_peer->peer,
+ GNUNET_i2s (multicast_peers[mc_peer->peer]->id));
+
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown();
+ }
+
+ if (0 == mc_peer->peer)
+ {
+ // Get GNUnet identity of members
+ for (int i = 0; i<PEERS_REQUESTED; i++)
+ {
+ pi_op[i] = GNUNET_TESTBED_peer_get_information (peers[i],
+ GNUNET_TESTBED_PIT_IDENTITY,
+ peer_information_cb,
+ multicast_peers[i]);
+ }
+ }
+}
+
+
+
+/**
+ * Main function inovked from TESTBED once all of the
+ * peers are up and running. This one then connects
+ * just to the multicast service of peer 0 and 1.
+ * Peer 0 is going to be origin.
+ * Peer 1 is going to be one member.
+ * Origin will start a multicast group and the member will try to join it.
+ * After that we execute some multicast test.
+ *
+ * @param cls closure
+ * @param h the run handle
+ * @param peers started peers for the test
+ * @param PEERS_REQUESTED size of the 'peers' array
+ * @param links_succeeded number of links between peers that were created
+ * @param links_failed number of links testbed was unable to establish
+ */
+static void
+testbed_master (void *cls,
+ struct GNUNET_TESTBED_RunHandle *h,
+ unsigned int num_peers,
+ struct GNUNET_TESTBED_Peer **p,
+ unsigned int links_succeeded,
+ unsigned int links_failed)
+{
+ /* Testbed is ready with peers running and connected in a pre-defined overlay
+ topology (FIXME) */
+ peers = p;
+ multicast_peers = GNUNET_new_array (PEERS_REQUESTED, struct MulticastPeerContext*);
+
+ // Create test contexts for members
+ for (int i = 0; i<PEERS_REQUESTED; i++)
+ {
+ multicast_peers[i] = GNUNET_new (struct MulticastPeerContext);
+ multicast_peers[i]->peer = i;
+ multicast_peers[i]->test_ok = GNUNET_NO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Create origin peer\n");
+ op[0] =
+ GNUNET_TESTBED_service_connect (/* Closure for operation */
+ NULL,
+ /* The peer whose service to connect to */
+ peers[0],
+ /* The name of the service */
+ "multicast",
+ /* called after a handle to service is opened */
+ service_connect,
+ /* closure for the above callback */
+ multicast_peers[0],
+ /* called when opening the service connection */
+ multicast_connect,
+ /* called when closing the service connection */
+ multicast_disconnect,
+ /* closure for the above two callbacks */
+ multicast_peers[0]);
+ /* Schedule a new task on shutdown */
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
+ /* Schedule the shutdown task with a delay of a few Seconds */
+ timeout_tid =
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_SECONDS, 400),
+ &timeout_task,
+ NULL);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ int ret;
+ char const *config_file;
+
+ if (strstr (argv[0], "_line") != NULL)
+ {
+ config_file = "test_multicast_line.conf";
+ }
+ else if (strstr(argv[0], "_star") != NULL)
+ {
+ config_file = "test_multicast_star.conf";
+ }
+ else
+ {
+ config_file = "test_multicast_star.conf";
+ }
+
+ result = GNUNET_SYSERR;
+ ret =
+ GNUNET_TESTBED_test_run ("test-multicast-multipeer",
+ config_file,
+ /* number of peers to start */
+ PEERS_REQUESTED,
+ /* Event mask - set to 0 for no event notifications */
+ 0LL,
+ /* Controller event callback */
+ NULL,
+ /* Closure for controller event callback */
+ NULL,
+ /* called when testbed setup is complete */
+ testbed_master,
+ /* Closure for the test_master callback */
+ NULL);
+ if ( (GNUNET_OK != ret) || (GNUNET_OK != result) )
+ return 1;
+ return 0;
+}
+
+/* end of test_multicast_multipeer.c */
diff --git a/src/multicast/test_multicast_star.conf b/src/multicast/test_multicast_star.conf
new file mode 100644
index 0000000..516c0e3
--- /dev/null
+++ b/src/multicast/test_multicast_star.conf
@@ -0,0 +1,64 @@
+[testbed]
+HOSTNAME = localhost
+OVERLAY_TOPOLOGY = STAR
+
+[arm]
+GLOBAL_POSTFIX=-L ERROR
+
+[multicast]
+#PREFIX = tmux new-window gdb -x ./cmd.gdb --args
+#PREFIX = valgrind --leak-check=full
+UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock
+
+[vpn]
+START_ON_DEMAND = NO
+
+[peerinfo]
+# Do not use shipped gnunet HELLOs
+USE_INCLUDED_HELLOS = NO
+
+# Option to disable all disk IO; only useful for testbed runs
+# (large-scale experiments); disables persistence of HELLOs!
+NO_IO = YES
+
+[cadet]
+ID_ANNOUNCE_TIME = 5 s
+
+[hostlist]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[nat]
+ENABLE_UPNP = NO
+
+[fs]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[vpn]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[revocation]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[gns]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[namestore]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[namecache]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[topology]
+IMMEDIATE_START = NO
+START_ON_DEMAND = NO
+
+[nse]
+WORKBITS = 0
+
diff --git a/src/psyc/.gitignore b/src/psyc/.gitignore
new file mode 100644
index 0000000..14a1753
--- /dev/null
+++ b/src/psyc/.gitignore
@@ -0,0 +1,2 @@
+gnunet-service-psyc
+test_psyc
diff --git a/src/psyc/Makefile.am b/src/psyc/Makefile.am
new file mode 100644
index 0000000..511e3e3
--- /dev/null
+++ b/src/psyc/Makefile.am
@@ -0,0 +1,77 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir= $(pkgdatadir)/config.d/
+
+libexecdir= $(pkglibdir)/libexec/
+
+pkgcfg_DATA = \
+ psyc.conf
+
+
+if MINGW
+ WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols
+endif
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = libgnunetpsyc.la
+
+libgnunetpsyc_la_SOURCES = \
+ psyc_api.c psyc.h
+libgnunetpsyc_la_LIBADD = \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(top_builddir)/src/psycutil/libgnunetpsycutil.la \
+ $(GN_LIBINTL) $(XLIB)
+libgnunetpsyc_la_LDFLAGS = \
+ $(GN_LIB_LDFLAGS) $(WINFLAGS) \
+ -version-info 0:0:0
+
+bin_PROGRAMS =
+
+libexec_PROGRAMS = \
+ gnunet-service-psyc
+
+gnunet_service_psyc_SOURCES = \
+ gnunet-service-psyc.c
+gnunet_service_psyc_LDADD = \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(top_builddir)/src/statistics/libgnunetstatistics.la \
+ $(top_builddir)/src/multicast/libgnunetmulticast.la \
+ $(top_builddir)/src/psycstore/libgnunetpsycstore.la \
+ $(top_builddir)/src/psycutil/libgnunetpsycutil.la \
+ $(GN_LIBINTL)
+gnunet_service_psyc_CFLAGS = $(AM_CFLAGS)
+
+
+if HAVE_TESTING
+check_PROGRAMS = \
+ test_psyc
+# test_psyc2
+endif
+
+if ENABLE_TEST_RUN
+AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
+TESTS = $(check_PROGRAMS)
+endif
+
+test_psyc_SOURCES = \
+ test_psyc.c
+test_psyc_LDADD = \
+ libgnunetpsyc.la \
+ $(top_builddir)/src/psycutil/libgnunetpsycutil.la \
+ $(top_builddir)/src/testing/libgnunettesting.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+#test_psyc2_SOURCES = \
+# test_psyc2.c
+#test_psyc2_LDADD = \
+# libgnunetpsyc.la \
+# $(top_builddir)/src/psycutil/libgnunetpsycutil.la \
+# $(top_builddir)/src/testbed/libgnunettestbed.la \
+# $(top_builddir)/src/util/libgnunetutil.la
+
+EXTRA_DIST = \
+ test_psyc.conf
diff --git a/src/psyc/gnunet-service-psyc.c b/src/psyc/gnunet-service-psyc.c
new file mode 100644
index 0000000..6f2f7a9
--- /dev/null
+++ b/src/psyc/gnunet-service-psyc.c
@@ -0,0 +1,2860 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/gnunet-service-psyc.c
+ * @brief PSYC service
+ * @author Gabor X Toth
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_constants.h"
+#include "gnunet_protocols.h"
+#include "gnunet_statistics_service.h"
+#include "gnunet_multicast_service.h"
+#include "gnunet_psycstore_service.h"
+#include "gnunet_psyc_service.h"
+#include "gnunet_psyc_util_lib.h"
+#include "psyc.h"
+
+
+/**
+ * Handle to our current configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Service handle.
+ */
+static struct GNUNET_SERVICE_Handle *service;
+
+/**
+ * Handle to the statistics service.
+ */
+static struct GNUNET_STATISTICS_Handle *stats;
+
+/**
+ * Handle to the PSYCstore.
+ */
+static struct GNUNET_PSYCSTORE_Handle *store;
+
+/**
+ * All connected masters.
+ * Channel's pub_key_hash -> struct Master
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *masters;
+
+/**
+ * All connected slaves.
+ * Channel's pub_key_hash -> struct Slave
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *slaves;
+
+/**
+ * Connected slaves per channel.
+ * Channel's pub_key_hash -> Slave's pub_key -> struct Slave
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *channel_slaves;
+
+
+/**
+ * Message in the transmission queue.
+ */
+struct TransmitMessage
+{
+ struct TransmitMessage *prev;
+ struct TransmitMessage *next;
+
+ struct GNUNET_SERVICE_Client *client;
+
+ /**
+ * ID assigned to the message.
+ */
+ uint64_t id;
+
+ /**
+ * Size of message.
+ */
+ uint16_t size;
+
+ /**
+ * Type of first message part.
+ */
+ uint16_t first_ptype;
+
+ /**
+ * Type of last message part.
+ */
+ uint16_t last_ptype;
+
+ /* Followed by message */
+};
+
+
+/**
+ * Cache for received message fragments.
+ * Message fragments are only sent to clients after all modifiers arrived.
+ *
+ * chan_key -> MultiHashMap chan_msgs
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *recv_cache;
+
+
+/**
+ * Entry in the chan_msgs hashmap of @a recv_cache:
+ * fragment_id -> RecvCacheEntry
+ */
+struct RecvCacheEntry
+{
+ struct GNUNET_MULTICAST_MessageHeader *mmsg;
+ uint16_t ref_count;
+};
+
+
+/**
+ * Entry in the @a recv_frags hash map of a @a Channel.
+ * message_id -> FragmentQueue
+ */
+struct FragmentQueue
+{
+ /**
+ * Fragment IDs stored in @a recv_cache.
+ */
+ struct GNUNET_CONTAINER_Heap *fragments;
+
+ /**
+ * Total size of received fragments.
+ */
+ uint64_t size;
+
+ /**
+ * Total size of received header fragments (METHOD & MODIFIERs)
+ */
+ uint64_t header_size;
+
+ /**
+ * The @a state_delta field from struct GNUNET_PSYC_MessageMethod.
+ */
+ uint64_t state_delta;
+
+ /**
+ * The @a flags field from struct GNUNET_PSYC_MessageMethod.
+ */
+ uint32_t flags;
+
+ /**
+ * Receive state of message.
+ *
+ * @see MessageFragmentState
+ */
+ uint8_t state;
+
+ /**
+ * Whether the state is already modified in PSYCstore.
+ */
+ uint8_t state_is_modified;
+
+ /**
+ * Is the message queued for delivery to the client?
+ * i.e. added to the recv_msgs queue
+ */
+ uint8_t is_queued;
+};
+
+
+/**
+ * List of connected clients.
+ */
+struct ClientList
+{
+ struct ClientList *prev;
+ struct ClientList *next;
+
+ struct GNUNET_SERVICE_Client *client;
+};
+
+
+struct Operation
+{
+ struct Operation *prev;
+ struct Operation *next;
+
+ struct GNUNET_SERVICE_Client *client;
+ struct Channel *channel;
+ uint64_t op_id;
+ uint32_t flags;
+};
+
+
+/**
+ * Common part of the client context for both a channel master and slave.
+ */
+struct Channel
+{
+ struct ClientList *clients_head;
+ struct ClientList *clients_tail;
+
+ struct Operation *op_head;
+ struct Operation *op_tail;
+
+ struct TransmitMessage *tmit_head;
+ struct TransmitMessage *tmit_tail;
+
+ /**
+ * Current PSYCstore operation.
+ */
+ struct GNUNET_PSYCSTORE_OperationHandle *store_op;
+
+ /**
+ * Received fragments not yet sent to the client.
+ * message_id -> FragmentQueue
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *recv_frags;
+
+ /**
+ * Received message IDs not yet sent to the client.
+ */
+ struct GNUNET_CONTAINER_Heap *recv_msgs;
+
+ /**
+ * Public key of the channel.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey pub_key;
+
+ /**
+ * Hash of @a pub_key.
+ */
+ struct GNUNET_HashCode pub_key_hash;
+
+ /**
+ * Last message ID sent to the client.
+ * 0 if there is no such message.
+ */
+ uint64_t max_message_id;
+
+ /**
+ * ID of the last stateful message, where the state operations has been
+ * processed and saved to PSYCstore and which has been sent to the client.
+ * 0 if there is no such message.
+ */
+ uint64_t max_state_message_id;
+
+ /**
+ * Expected value size for the modifier being received from the PSYC service.
+ */
+ uint32_t tmit_mod_value_size_expected;
+
+ /**
+ * Actual value size for the modifier being received from the PSYC service.
+ */
+ uint32_t tmit_mod_value_size;
+
+ /**
+ * Is this channel ready to receive messages from client?
+ * #GNUNET_YES or #GNUNET_NO
+ */
+ uint8_t is_ready;
+
+ /**
+ * Is the client disconnected?
+ * #GNUNET_YES or #GNUNET_NO
+ */
+ uint8_t is_disconnecting;
+
+ /**
+ * Is this a channel master (#GNUNET_YES), or slave (#GNUNET_NO)?
+ */
+ uint8_t is_master;
+
+ union {
+ struct Master *master;
+ struct Slave *slave;
+ };
+};
+
+
+/**
+ * Client context for a channel master.
+ */
+struct Master
+{
+ /**
+ * Channel struct common for Master and Slave
+ */
+ struct Channel channel;
+
+ /**
+ * Private key of the channel.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv_key;
+
+ /**
+ * Handle for the multicast origin.
+ */
+ struct GNUNET_MULTICAST_Origin *origin;
+
+ /**
+ * Transmit handle for multicast.
+ */
+ struct GNUNET_MULTICAST_OriginTransmitHandle *tmit_handle;
+
+ /**
+ * Incoming join requests from multicast.
+ * member_pub_key -> struct GNUNET_MULTICAST_JoinHandle *
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *join_reqs;
+
+ /**
+ * Last message ID transmitted to this channel.
+ *
+ * Incremented before sending a message, thus the message_id in messages sent
+ * starts from 1.
+ */
+ uint64_t max_message_id;
+
+ /**
+ * ID of the last message with state operations transmitted to the channel.
+ * 0 if there is no such message.
+ */
+ uint64_t max_state_message_id;
+
+ /**
+ * Maximum group generation transmitted to the channel.
+ */
+ uint64_t max_group_generation;
+
+ /**
+ * @see enum GNUNET_PSYC_Policy
+ */
+ enum GNUNET_PSYC_Policy policy;
+};
+
+
+/**
+ * Client context for a channel slave.
+ */
+struct Slave
+{
+ /**
+ * Channel struct common for Master and Slave
+ */
+ struct Channel channel;
+
+ /**
+ * Private key of the slave.
+ */
+ struct GNUNET_CRYPTO_EcdsaPrivateKey priv_key;
+
+ /**
+ * Public key of the slave.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey pub_key;
+
+ /**
+ * Hash of @a pub_key.
+ */
+ struct GNUNET_HashCode pub_key_hash;
+
+ /**
+ * Handle for the multicast member.
+ */
+ struct GNUNET_MULTICAST_Member *member;
+
+ /**
+ * Transmit handle for multicast.
+ */
+ struct GNUNET_MULTICAST_MemberTransmitHandle *tmit_handle;
+
+ /**
+ * Peer identity of the origin.
+ */
+ struct GNUNET_PeerIdentity origin;
+
+ /**
+ * Number of items in @a relays.
+ */
+ uint32_t relay_count;
+
+ /**
+ * Relays that multicast can use to connect.
+ */
+ struct GNUNET_PeerIdentity *relays;
+
+ /**
+ * Join request to be transmitted to the master on join.
+ */
+ struct GNUNET_PSYC_Message *join_msg;
+
+ /**
+ * Join decision received from multicast.
+ */
+ struct GNUNET_PSYC_JoinDecisionMessage *join_dcsn;
+
+ /**
+ * Maximum request ID for this channel.
+ */
+ uint64_t max_request_id;
+
+ /**
+ * Join flags.
+ */
+ enum GNUNET_PSYC_SlaveJoinFlags join_flags;
+};
+
+
+/**
+ * Client context.
+ */
+struct Client {
+ struct GNUNET_SERVICE_Client *client;
+ struct Channel *channel;
+};
+
+
+struct ReplayRequestKey
+{
+ uint64_t fragment_id;
+ uint64_t message_id;
+ uint64_t fragment_offset;
+ uint64_t flags;
+};
+
+
+static void
+transmit_message (struct Channel *chn);
+
+static uint64_t
+message_queue_run (struct Channel *chn);
+
+static uint64_t
+message_queue_drop (struct Channel *chn);
+
+
+static void
+schedule_transmit_message (void *cls)
+{
+ struct Channel *chn = cls;
+
+ transmit_message (chn);
+}
+
+
+/**
+ * Task run during shutdown.
+ *
+ * @param cls unused
+ */
+static void
+shutdown_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "shutting down...\n");
+ GNUNET_PSYCSTORE_disconnect (store);
+ if (NULL != stats)
+ {
+ GNUNET_STATISTICS_destroy (stats, GNUNET_YES);
+ stats = NULL;
+ }
+}
+
+
+static struct Operation *
+op_add (struct Channel *chn, struct GNUNET_SERVICE_Client *client,
+ uint64_t op_id, uint32_t flags)
+{
+ struct Operation *op = GNUNET_malloc (sizeof (*op));
+ op->client = client;
+ op->channel = chn;
+ op->op_id = op_id;
+ op->flags = flags;
+ GNUNET_CONTAINER_DLL_insert (chn->op_head, chn->op_tail, op);
+ return op;
+}
+
+
+static void
+op_remove (struct Operation *op)
+{
+ GNUNET_CONTAINER_DLL_remove (op->channel->op_head, op->channel->op_tail, op);
+ GNUNET_free (op);
+}
+
+
+/**
+ * Clean up master data structures after a client disconnected.
+ */
+static void
+cleanup_master (struct Master *mst)
+{
+ struct Channel *chn = &mst->channel;
+
+ GNUNET_CONTAINER_multihashmap_destroy (mst->join_reqs);
+ GNUNET_CONTAINER_multihashmap_remove (masters, &chn->pub_key_hash, mst);
+}
+
+
+/**
+ * Clean up slave data structures after a client disconnected.
+ */
+static void
+cleanup_slave (struct Slave *slv)
+{
+ struct Channel *chn = &slv->channel;
+ struct GNUNET_CONTAINER_MultiHashMap *
+ chn_slv = GNUNET_CONTAINER_multihashmap_get (channel_slaves,
+ &chn->pub_key_hash);
+ GNUNET_assert (NULL != chn_slv);
+ GNUNET_CONTAINER_multihashmap_remove (chn_slv, &slv->pub_key_hash, slv);
+
+ if (0 == GNUNET_CONTAINER_multihashmap_size (chn_slv))
+ {
+ GNUNET_CONTAINER_multihashmap_remove (channel_slaves, &chn->pub_key_hash,
+ chn_slv);
+ GNUNET_CONTAINER_multihashmap_destroy (chn_slv);
+ }
+ GNUNET_CONTAINER_multihashmap_remove (slaves, &chn->pub_key_hash, slv);
+
+ if (NULL != slv->join_msg)
+ {
+ GNUNET_free (slv->join_msg);
+ slv->join_msg = NULL;
+ }
+ if (NULL != slv->relays)
+ {
+ GNUNET_free (slv->relays);
+ slv->relays = NULL;
+ }
+ GNUNET_CONTAINER_multihashmap_remove (slaves, &chn->pub_key_hash, slv);
+}
+
+
+/**
+ * Clean up channel data structures after a client disconnected.
+ */
+static void
+cleanup_channel (struct Channel *chn)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Cleaning up channel %s. master? %u\n",
+ chn,
+ GNUNET_h2s (&chn->pub_key_hash),
+ chn->is_master);
+ message_queue_drop (chn);
+ GNUNET_CONTAINER_multihashmap_destroy (chn->recv_frags);
+ chn->recv_frags = NULL;
+
+ if (NULL != chn->store_op)
+ {
+ GNUNET_PSYCSTORE_operation_cancel (chn->store_op);
+ chn->store_op = NULL;
+ }
+
+ (GNUNET_YES == chn->is_master)
+ ? cleanup_master (chn->master)
+ : cleanup_slave (chn->slave);
+ GNUNET_free (chn);
+}
+
+
+/**
+ * Called whenever a client is disconnected.
+ * Frees our resources associated with that client.
+ *
+ * @param cls closure
+ * @param client identification of the client
+ * @param app_ctx must match @a client
+ */
+static void
+client_notify_disconnect (void *cls,
+ struct GNUNET_SERVICE_Client *client,
+ void *app_ctx)
+{
+ struct Client *c = app_ctx;
+ struct Channel *chn = c->channel;
+ GNUNET_free (c);
+
+ if (NULL == chn)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p User context is NULL in client_notify_disconnect ()\n",
+ chn);
+ GNUNET_break (0);
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Client %p (%s) disconnected from channel %s\n",
+ chn,
+ client,
+ (GNUNET_YES == chn->is_master) ? "master" : "slave",
+ GNUNET_h2s (&chn->pub_key_hash));
+
+ struct ClientList *cli = chn->clients_head;
+ while (NULL != cli)
+ {
+ if (cli->client == client)
+ {
+ GNUNET_CONTAINER_DLL_remove (chn->clients_head, chn->clients_tail, cli);
+ GNUNET_free (cli);
+ break;
+ }
+ cli = cli->next;
+ }
+
+ struct Operation *op = chn->op_head;
+ while (NULL != op)
+ {
+ if (op->client == client)
+ {
+ op->client = NULL;
+ break;
+ }
+ op = op->next;
+ }
+
+ if (NULL == chn->clients_head)
+ { /* Last client disconnected. */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Last client (%s) disconnected from channel %s\n",
+ chn,
+ (GNUNET_YES == chn->is_master) ? "master" : "slave",
+ GNUNET_h2s (&chn->pub_key_hash));
+ chn->is_disconnecting = GNUNET_YES;
+ cleanup_channel (chn);
+ }
+}
+
+
+/**
+ * A new client connected.
+ *
+ * @param cls NULL
+ * @param client client to add
+ * @param mq message queue for @a client
+ * @return @a client
+ */
+static void *
+client_notify_connect (void *cls,
+ struct GNUNET_SERVICE_Client *client,
+ struct GNUNET_MQ_Handle *mq)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client connected: %p\n", client);
+
+ struct Client *c = GNUNET_malloc (sizeof (*c));
+ c->client = client;
+
+ return c;
+}
+
+
+/**
+ * Send message to all clients connected to the channel.
+ */
+static void
+client_send_msg (const struct Channel *chn,
+ const struct GNUNET_MessageHeader *msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending message to clients of channel %p.\n",
+ chn);
+
+ struct ClientList *cli = chn->clients_head;
+ while (NULL != cli)
+ {
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_copy (msg);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (cli->client),
+ env);
+ cli = cli->next;
+ }
+}
+
+
+/**
+ * Send a result code back to the client.
+ *
+ * @param client
+ * Client that should receive the result code.
+ * @param result_code
+ * Code to transmit.
+ * @param op_id
+ * Operation ID in network byte order.
+ * @param data
+ * Data payload or NULL.
+ * @param data_size
+ * Size of @a data.
+ */
+static void
+client_send_result (struct GNUNET_SERVICE_Client *client, uint64_t op_id,
+ int64_t result_code, const void *data, uint16_t data_size)
+{
+ struct GNUNET_OperationResultMessage *res;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (res,
+ data_size,
+ GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE);
+ res->result_code = GNUNET_htonll (result_code);
+ res->op_id = op_id;
+ if (0 < data_size)
+ GNUNET_memcpy (&res[1], data, data_size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Sending result to client for OP ID %" PRIu64 ": %" PRId64 " (size: %u)\n",
+ client,
+ GNUNET_ntohll (op_id),
+ result_code,
+ data_size);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
+}
+
+
+/**
+ * Closure for join_mem_test_cb()
+ */
+struct JoinMemTestClosure
+{
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+ struct Channel *channel;
+ struct GNUNET_MULTICAST_JoinHandle *join_handle;
+ struct GNUNET_PSYC_JoinRequestMessage *join_msg;
+};
+
+
+/**
+ * Membership test result callback used for join requests.
+ */
+static void
+join_mem_test_cb (void *cls, int64_t result,
+ const char *err_msg, uint16_t err_msg_size)
+{
+ struct JoinMemTestClosure *jcls = cls;
+
+ if (GNUNET_NO == result && GNUNET_YES == jcls->channel->is_master)
+ { /* Pass on join request to client if this is a master channel */
+ struct Master *mst = jcls->channel->master;
+ struct GNUNET_HashCode slave_pub_hash;
+ GNUNET_CRYPTO_hash (&jcls->slave_pub_key, sizeof (jcls->slave_pub_key),
+ &slave_pub_hash);
+ GNUNET_CONTAINER_multihashmap_put (mst->join_reqs, &slave_pub_hash, jcls->join_handle,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ client_send_msg (jcls->channel, &jcls->join_msg->header);
+ }
+ else
+ {
+ if (GNUNET_SYSERR == result)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not perform membership test (%.*s)\n",
+ err_msg_size, err_msg);
+ }
+ // FIXME: add relays
+ GNUNET_MULTICAST_join_decision (jcls->join_handle, result, 0, NULL, NULL);
+ }
+ GNUNET_free (jcls->join_msg);
+ GNUNET_free (jcls);
+}
+
+
+/**
+ * Incoming join request from multicast.
+ */
+static void
+mcast_recv_join_request (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ const struct GNUNET_MessageHeader *join_msg,
+ struct GNUNET_MULTICAST_JoinHandle *jh)
+{
+ struct Channel *chn = cls;
+ uint16_t join_msg_size = 0;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Got join request.\n",
+ chn);
+ if (NULL != join_msg)
+ {
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE == ntohs (join_msg->type))
+ {
+ join_msg_size = ntohs (join_msg->size);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "%p Got join message with invalid type %u.\n",
+ chn,
+ ntohs (join_msg->type));
+ }
+ }
+
+ struct GNUNET_PSYC_JoinRequestMessage *
+ req = GNUNET_malloc (sizeof (*req) + join_msg_size);
+ req->header.size = htons (sizeof (*req) + join_msg_size);
+ req->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_JOIN_REQUEST);
+ req->slave_pub_key = *slave_pub_key;
+ if (0 < join_msg_size)
+ GNUNET_memcpy (&req[1], join_msg, join_msg_size);
+
+ struct JoinMemTestClosure *jcls = GNUNET_malloc (sizeof (*jcls));
+ jcls->slave_pub_key = *slave_pub_key;
+ jcls->channel = chn;
+ jcls->join_handle = jh;
+ jcls->join_msg = req;
+
+ GNUNET_PSYCSTORE_membership_test (store, &chn->pub_key, slave_pub_key,
+ chn->max_message_id, 0,
+ &join_mem_test_cb, jcls);
+}
+
+
+/**
+ * Join decision received from multicast.
+ */
+static void
+mcast_recv_join_decision (void *cls, int is_admitted,
+ const struct GNUNET_PeerIdentity *peer,
+ uint16_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_MessageHeader *join_resp)
+{
+ struct Slave *slv = cls;
+ struct Channel *chn = &slv->channel;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Got join decision: %d\n",
+ slv,
+ is_admitted);
+ if (GNUNET_YES == chn->is_ready)
+ {
+ /* Already admitted */
+ return;
+ }
+
+ uint16_t join_resp_size = (NULL != join_resp) ? ntohs (join_resp->size) : 0;
+ struct GNUNET_PSYC_JoinDecisionMessage *
+ dcsn = slv->join_dcsn = GNUNET_malloc (sizeof (*dcsn) + join_resp_size);
+ dcsn->header.size = htons (sizeof (*dcsn) + join_resp_size);
+ dcsn->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION);
+ dcsn->is_admitted = htonl (is_admitted);
+ if (0 < join_resp_size)
+ GNUNET_memcpy (&dcsn[1], join_resp, join_resp_size);
+
+ client_send_msg (chn, &dcsn->header);
+
+ if (GNUNET_YES == is_admitted
+ && ! (GNUNET_PSYC_SLAVE_JOIN_LOCAL & slv->join_flags))
+ {
+ chn->is_ready = GNUNET_YES;
+ }
+}
+
+
+static int
+store_recv_fragment_replay (void *cls,
+ struct GNUNET_MULTICAST_MessageHeader *msg,
+ enum GNUNET_PSYCSTORE_MessageFlags flags)
+{
+ struct GNUNET_MULTICAST_ReplayHandle *rh = cls;
+
+ GNUNET_MULTICAST_replay_response (rh, &msg->header, GNUNET_MULTICAST_REC_OK);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Received result of GNUNET_PSYCSTORE_fragment_get() for multicast replay.
+ */
+static void
+store_recv_fragment_replay_result (void *cls,
+ int64_t result,
+ const char *err_msg,
+ uint16_t err_msg_size)
+{
+ struct GNUNET_MULTICAST_ReplayHandle *rh = cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Fragment replay: PSYCSTORE returned %" PRId64 " (%.*s)\n",
+ rh,
+ result,
+ err_msg_size,
+ err_msg);
+ switch (result)
+ {
+ case GNUNET_YES:
+ break;
+
+ case GNUNET_NO:
+ GNUNET_MULTICAST_replay_response (rh, NULL,
+ GNUNET_MULTICAST_REC_NOT_FOUND);
+ return;
+
+ case GNUNET_PSYCSTORE_MEMBERSHIP_TEST_FAILED:
+ GNUNET_MULTICAST_replay_response (rh, NULL,
+ GNUNET_MULTICAST_REC_ACCESS_DENIED);
+ return;
+
+ case GNUNET_SYSERR:
+ GNUNET_MULTICAST_replay_response (rh, NULL,
+ GNUNET_MULTICAST_REC_INTERNAL_ERROR);
+ return;
+ }
+ /* GNUNET_MULTICAST_replay_response frees 'rh' when passed
+ * an error code, so it must be ensured no further processing
+ * is attempted on 'rh'. Maybe this should be refactored as
+ * it doesn't look very intuitive. --lynX
+ */
+ GNUNET_MULTICAST_replay_response_end (rh);
+}
+
+
+/**
+ * Incoming fragment replay request from multicast.
+ */
+static void
+mcast_recv_replay_fragment (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ uint64_t fragment_id, uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+
+{
+ struct Channel *chn = cls;
+ GNUNET_PSYCSTORE_fragment_get (store, &chn->pub_key, slave_pub_key,
+ fragment_id, fragment_id,
+ &store_recv_fragment_replay,
+ &store_recv_fragment_replay_result, rh);
+}
+
+
+/**
+ * Incoming message replay request from multicast.
+ */
+static void
+mcast_recv_replay_message (void *cls,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ uint64_t message_id,
+ uint64_t fragment_offset,
+ uint64_t flags,
+ struct GNUNET_MULTICAST_ReplayHandle *rh)
+{
+ struct Channel *chn = cls;
+ GNUNET_PSYCSTORE_message_get (store, &chn->pub_key, slave_pub_key,
+ message_id, message_id, 1, NULL,
+ &store_recv_fragment_replay,
+ &store_recv_fragment_replay_result, rh);
+}
+
+
+/**
+ * Convert an uint64_t in network byte order to a HashCode
+ * that can be used as key in a MultiHashMap
+ */
+static inline void
+hash_key_from_nll (struct GNUNET_HashCode *key, uint64_t n)
+{
+ /* use little-endian order, as idx_of MultiHashMap casts key to unsigned int */
+ /* TODO: use built-in byte swap functions if available */
+
+ n = ((n << 8) & 0xFF00FF00FF00FF00ULL) | ((n >> 8) & 0x00FF00FF00FF00FFULL);
+ n = ((n << 16) & 0xFFFF0000FFFF0000ULL) | ((n >> 16) & 0x0000FFFF0000FFFFULL);
+
+ *key = (struct GNUNET_HashCode) {};
+ *((uint64_t *) key)
+ = (n << 32) | (n >> 32);
+}
+
+
+/**
+ * Convert an uint64_t in host byte order to a HashCode
+ * that can be used as key in a MultiHashMap
+ */
+static inline void
+hash_key_from_hll (struct GNUNET_HashCode *key, uint64_t n)
+{
+#if __BYTE_ORDER == __BIG_ENDIAN
+ hash_key_from_nll (key, n);
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ *key = (struct GNUNET_HashCode) {};
+ *((uint64_t *) key) = n;
+#else
+ #error byteorder undefined
+#endif
+}
+
+
+/**
+ * Initialize PSYC message header.
+ */
+static inline void
+psyc_msg_init (struct GNUNET_PSYC_MessageHeader *pmsg,
+ const struct GNUNET_MULTICAST_MessageHeader *mmsg, uint32_t flags)
+{
+ uint16_t size = ntohs (mmsg->header.size);
+ uint16_t psize = sizeof (*pmsg) + size - sizeof (*mmsg);
+
+ pmsg->header.size = htons (psize);
+ pmsg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE);
+ pmsg->message_id = mmsg->message_id;
+ pmsg->fragment_offset = mmsg->fragment_offset;
+ pmsg->flags = htonl (flags);
+
+ GNUNET_memcpy (&pmsg[1], &mmsg[1], size - sizeof (*mmsg));
+}
+
+
+/**
+ * Create a new PSYC message from a multicast message for sending it to clients.
+ */
+static inline struct GNUNET_PSYC_MessageHeader *
+psyc_msg_new (const struct GNUNET_MULTICAST_MessageHeader *mmsg, uint32_t flags)
+{
+ struct GNUNET_PSYC_MessageHeader *pmsg;
+ uint16_t size = ntohs (mmsg->header.size);
+ uint16_t psize = sizeof (*pmsg) + size - sizeof (*mmsg);
+
+ pmsg = GNUNET_malloc (psize);
+ psyc_msg_init (pmsg, mmsg, flags);
+ return pmsg;
+}
+
+
+/**
+ * Send multicast message to all clients connected to the channel.
+ */
+static void
+client_send_mcast_msg (struct Channel *chn,
+ const struct GNUNET_MULTICAST_MessageHeader *mmsg,
+ uint32_t flags)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Sending multicast message to client. fragment_id: %" PRIu64 ", message_id: %" PRIu64 "\n",
+ chn,
+ GNUNET_ntohll (mmsg->fragment_id),
+ GNUNET_ntohll (mmsg->message_id));
+
+ struct GNUNET_PSYC_MessageHeader *
+ pmsg = GNUNET_PSYC_message_header_create (mmsg, flags);
+ client_send_msg (chn, &pmsg->header);
+ GNUNET_free (pmsg);
+}
+
+
+/**
+ * Send multicast request to all clients connected to the channel.
+ */
+static void
+client_send_mcast_req (struct Master *mst,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ struct Channel *chn = &mst->channel;
+
+ struct GNUNET_PSYC_MessageHeader *pmsg;
+ uint16_t size = ntohs (req->header.size);
+ uint16_t psize = sizeof (*pmsg) + size - sizeof (*req);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Sending multicast request to client. fragment_id: %" PRIu64 ", message_id: %" PRIu64 "\n",
+ chn,
+ GNUNET_ntohll (req->fragment_id),
+ GNUNET_ntohll (req->request_id));
+
+ pmsg = GNUNET_malloc (psize);
+ pmsg->header.size = htons (psize);
+ pmsg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE);
+ pmsg->message_id = req->request_id;
+ pmsg->fragment_offset = req->fragment_offset;
+ pmsg->flags = htonl (GNUNET_PSYC_MESSAGE_REQUEST);
+ pmsg->slave_pub_key = req->member_pub_key;
+ GNUNET_memcpy (&pmsg[1], &req[1], size - sizeof (*req));
+
+ client_send_msg (chn, &pmsg->header);
+
+ /* FIXME: save req to PSYCstore so that it can be resent later to clients */
+
+ GNUNET_free (pmsg);
+}
+
+
+/**
+ * Insert a multicast message fragment into the queue belonging to the message.
+ *
+ * @param chn Channel.
+ * @param mmsg Multicast message fragment.
+ * @param msg_id_hash Message ID of @a mmsg in a struct GNUNET_HashCode.
+ * @param first_ptype First PSYC message part type in @a mmsg.
+ * @param last_ptype Last PSYC message part type in @a mmsg.
+ */
+static void
+fragment_queue_insert (struct Channel *chn,
+ const struct GNUNET_MULTICAST_MessageHeader *mmsg,
+ uint16_t first_ptype, uint16_t last_ptype)
+{
+ const uint16_t size = ntohs (mmsg->header.size);
+ const uint64_t frag_offset = GNUNET_ntohll (mmsg->fragment_offset);
+ struct GNUNET_CONTAINER_MultiHashMap
+ *chan_msgs = GNUNET_CONTAINER_multihashmap_get (recv_cache,
+ &chn->pub_key_hash);
+
+ struct GNUNET_HashCode msg_id_hash;
+ hash_key_from_nll (&msg_id_hash, mmsg->message_id);
+
+ struct FragmentQueue
+ *fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &msg_id_hash);
+
+ if (NULL == fragq)
+ {
+ fragq = GNUNET_malloc (sizeof (*fragq));
+ fragq->state = MSG_FRAG_STATE_HEADER;
+ fragq->fragments
+ = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
+
+ GNUNET_CONTAINER_multihashmap_put (chn->recv_frags, &msg_id_hash, fragq,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+
+ if (NULL == chan_msgs)
+ {
+ chan_msgs = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ GNUNET_CONTAINER_multihashmap_put (recv_cache, &chn->pub_key_hash, chan_msgs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ }
+ }
+
+ struct GNUNET_HashCode frag_id_hash;
+ hash_key_from_nll (&frag_id_hash, mmsg->fragment_id);
+ struct RecvCacheEntry
+ *cache_entry = GNUNET_CONTAINER_multihashmap_get (chan_msgs, &frag_id_hash);
+ if (NULL == cache_entry)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Adding message fragment to cache. message_id: %" PRIu64 ", fragment_id: %" PRIu64 "\n",
+ chn,
+ GNUNET_ntohll (mmsg->message_id),
+ GNUNET_ntohll (mmsg->fragment_id));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p header_size: %" PRIu64 " + %u\n",
+ chn,
+ fragq->header_size,
+ size);
+ cache_entry = GNUNET_malloc (sizeof (*cache_entry));
+ cache_entry->ref_count = 1;
+ cache_entry->mmsg = GNUNET_malloc (size);
+ GNUNET_memcpy (cache_entry->mmsg, mmsg, size);
+ GNUNET_CONTAINER_multihashmap_put (chan_msgs, &frag_id_hash, cache_entry,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ }
+ else
+ {
+ cache_entry->ref_count++;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Message fragment is already in cache. message_id: %" PRIu64 ", fragment_id: %" PRIu64 ", ref_count: %u\n",
+ chn,
+ GNUNET_ntohll (mmsg->message_id),
+ GNUNET_ntohll (mmsg->fragment_id),
+ cache_entry->ref_count);
+ }
+
+ if (MSG_FRAG_STATE_HEADER == fragq->state)
+ {
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype)
+ {
+ struct GNUNET_PSYC_MessageMethod *
+ pmeth = (struct GNUNET_PSYC_MessageMethod *) &mmsg[1];
+ fragq->state_delta = GNUNET_ntohll (pmeth->state_delta);
+ fragq->flags = ntohl (pmeth->flags);
+ }
+
+ if (last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA)
+ {
+ fragq->header_size += size;
+ }
+ else if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype
+ || frag_offset == fragq->header_size)
+ { /* header is now complete */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Header of message %" PRIu64 " is complete.\n",
+ chn,
+ GNUNET_ntohll (mmsg->message_id));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Adding message %" PRIu64 " to queue.\n",
+ chn,
+ GNUNET_ntohll (mmsg->message_id));
+ fragq->state = MSG_FRAG_STATE_DATA;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Header of message %" PRIu64 " is NOT complete yet: %" PRIu64 " != %" PRIu64 "\n",
+ chn,
+ GNUNET_ntohll (mmsg->message_id),
+ frag_offset,
+ fragq->header_size);
+ }
+ }
+
+ switch (last_ptype)
+ {
+ case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END:
+ if (frag_offset == fragq->size)
+ fragq->state = MSG_FRAG_STATE_END;
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Message %" PRIu64 " is NOT complete yet: %" PRIu64 " != %" PRIu64 "\n",
+ chn,
+ GNUNET_ntohll (mmsg->message_id),
+ frag_offset,
+ fragq->size);
+ break;
+
+ case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL:
+ /* Drop message without delivering to client if it's a single fragment */
+ fragq->state =
+ (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype)
+ ? MSG_FRAG_STATE_DROP
+ : MSG_FRAG_STATE_CANCEL;
+ }
+
+ switch (fragq->state)
+ {
+ case MSG_FRAG_STATE_DATA:
+ case MSG_FRAG_STATE_END:
+ case MSG_FRAG_STATE_CANCEL:
+ if (GNUNET_NO == fragq->is_queued)
+ {
+ GNUNET_CONTAINER_heap_insert (chn->recv_msgs, NULL,
+ GNUNET_ntohll (mmsg->message_id));
+ fragq->is_queued = GNUNET_YES;
+ }
+ }
+
+ fragq->size += size;
+ GNUNET_CONTAINER_heap_insert (fragq->fragments, NULL,
+ GNUNET_ntohll (mmsg->fragment_id));
+}
+
+
+/**
+ * Run fragment queue of a message.
+ *
+ * Send fragments of a message in order to client, after all modifiers arrived
+ * from multicast.
+ *
+ * @param chn
+ * Channel.
+ * @param msg_id
+ * ID of the message @a fragq belongs to.
+ * @param fragq
+ * Fragment queue of the message.
+ * @param drop
+ * Drop message without delivering to client?
+ * #GNUNET_YES or #GNUNET_NO.
+ */
+static void
+fragment_queue_run (struct Channel *chn, uint64_t msg_id,
+ struct FragmentQueue *fragq, uint8_t drop)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Running message fragment queue for message %" PRIu64 " (state: %u).\n",
+ chn,
+ msg_id,
+ fragq->state);
+
+ struct GNUNET_CONTAINER_MultiHashMap
+ *chan_msgs = GNUNET_CONTAINER_multihashmap_get (recv_cache,
+ &chn->pub_key_hash);
+ GNUNET_assert (NULL != chan_msgs);
+ uint64_t frag_id;
+
+ while (GNUNET_YES == GNUNET_CONTAINER_heap_peek2 (fragq->fragments, NULL,
+ &frag_id))
+ {
+ struct GNUNET_HashCode frag_id_hash;
+ hash_key_from_hll (&frag_id_hash, frag_id);
+ struct RecvCacheEntry *cache_entry
+ = GNUNET_CONTAINER_multihashmap_get (chan_msgs, &frag_id_hash);
+ if (cache_entry != NULL)
+ {
+ if (GNUNET_NO == drop)
+ {
+ client_send_mcast_msg (chn, cache_entry->mmsg, 0);
+ }
+ if (cache_entry->ref_count <= 1)
+ {
+ GNUNET_CONTAINER_multihashmap_remove (chan_msgs, &frag_id_hash,
+ cache_entry);
+ GNUNET_free (cache_entry->mmsg);
+ GNUNET_free (cache_entry);
+ }
+ else
+ {
+ cache_entry->ref_count--;
+ }
+ }
+#if CACHE_AGING_IMPLEMENTED
+ else if (GNUNET_NO == drop)
+ {
+ /* TODO: fragment not in cache anymore, retrieve it from PSYCstore */
+ }
+#endif
+
+ GNUNET_CONTAINER_heap_remove_root (fragq->fragments);
+ }
+
+ if (MSG_FRAG_STATE_END <= fragq->state)
+ {
+ struct GNUNET_HashCode msg_id_hash;
+ hash_key_from_hll (&msg_id_hash, msg_id);
+
+ GNUNET_CONTAINER_multihashmap_remove (chn->recv_frags, &msg_id_hash, fragq);
+ GNUNET_CONTAINER_heap_destroy (fragq->fragments);
+ GNUNET_free (fragq);
+ }
+ else
+ {
+ fragq->is_queued = GNUNET_NO;
+ }
+}
+
+
+struct StateModifyClosure
+{
+ struct Channel *channel;
+ uint64_t msg_id;
+ struct GNUNET_HashCode msg_id_hash;
+};
+
+
+void
+store_recv_state_modify_result (void *cls, int64_t result,
+ const char *err_msg, uint16_t err_msg_size)
+{
+ struct StateModifyClosure *mcls = cls;
+ struct Channel *chn = mcls->channel;
+ uint64_t msg_id = mcls->msg_id;
+
+ struct FragmentQueue *
+ fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &mcls->msg_id_hash);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p GNUNET_PSYCSTORE_state_modify() returned %" PRId64 " (%.*s)\n",
+ chn, result, err_msg_size, err_msg);
+
+ switch (result)
+ {
+ case GNUNET_OK:
+ case GNUNET_NO:
+ if (NULL != fragq)
+ fragq->state_is_modified = GNUNET_YES;
+ if (chn->max_state_message_id < msg_id)
+ chn->max_state_message_id = msg_id;
+ if (chn->max_message_id < msg_id)
+ chn->max_message_id = msg_id;
+
+ if (NULL != fragq)
+ fragment_queue_run (chn, msg_id, fragq, MSG_FRAG_STATE_DROP == fragq->state);
+ GNUNET_CONTAINER_heap_remove_root (chn->recv_msgs);
+ message_queue_run (chn);
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p GNUNET_PSYCSTORE_state_modify() failed with error %" PRId64 " (%.*s)\n",
+ chn, result, err_msg_size, err_msg);
+ /** @todo FIXME: handle state_modify error */
+ }
+}
+
+
+/**
+ * Run message queue.
+ *
+ * Send messages in queue to client in order after a message has arrived from
+ * multicast, according to the following:
+ * - A message is only sent if all of its modifiers arrived.
+ * - A stateful message is only sent if the previous stateful message
+ * has already been delivered to the client.
+ *
+ * @param chn Channel.
+ *
+ * @return Number of messages removed from queue and sent to client.
+ */
+static uint64_t
+message_queue_run (struct Channel *chn)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Running message queue.\n", chn);
+ uint64_t n = 0;
+ uint64_t msg_id;
+
+ while (GNUNET_YES == GNUNET_CONTAINER_heap_peek2 (chn->recv_msgs, NULL,
+ &msg_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Processing message %" PRIu64 " in queue.\n", chn, msg_id);
+ struct GNUNET_HashCode msg_id_hash;
+ hash_key_from_hll (&msg_id_hash, msg_id);
+
+ struct FragmentQueue *
+ fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &msg_id_hash);
+
+ if (NULL == fragq || fragq->state <= MSG_FRAG_STATE_HEADER)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p No fragq (%p) or header not complete.\n",
+ chn, fragq);
+ break;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Fragment queue entry: state: %u, state delta: "
+ "%" PRIu64 " - %" PRIu64 " ?= %" PRIu64 "\n",
+ chn, fragq->state, msg_id, fragq->state_delta, chn->max_state_message_id);
+
+ if (MSG_FRAG_STATE_DATA <= fragq->state)
+ {
+ /* Check if there's a missing message before the current one */
+ if (GNUNET_PSYC_STATE_NOT_MODIFIED == fragq->state_delta)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%p state NOT modified\n", chn);
+
+ if (!(fragq->flags & GNUNET_PSYC_MESSAGE_ORDER_ANY)
+ && (chn->max_message_id != msg_id - 1
+ && chn->max_message_id != msg_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Out of order message. "
+ "(%" PRIu64 " != %" PRIu64 " - 1)\n",
+ chn, chn->max_message_id, msg_id);
+ break;
+ // FIXME: keep track of messages processed in this queue run,
+ // and only stop after reaching the end
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%p state modified\n", chn);
+ if (GNUNET_YES != fragq->state_is_modified)
+ {
+ if (msg_id - fragq->state_delta != chn->max_state_message_id)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Out of order stateful message. "
+ "(%" PRIu64 " - %" PRIu64 " != %" PRIu64 ")\n",
+ chn, msg_id, fragq->state_delta, chn->max_state_message_id);
+ break;
+ // FIXME: keep track of messages processed in this queue run,
+ // and only stop after reaching the end
+ }
+
+ struct StateModifyClosure *mcls = GNUNET_malloc (sizeof (*mcls));
+ mcls->channel = chn;
+ mcls->msg_id = msg_id;
+ mcls->msg_id_hash = msg_id_hash;
+
+ /* Apply modifiers to state in PSYCstore */
+ GNUNET_PSYCSTORE_state_modify (store, &chn->pub_key, msg_id,
+ fragq->state_delta,
+ store_recv_state_modify_result, mcls);
+ break; // continue after asynchronous state modify result
+ }
+ }
+ chn->max_message_id = msg_id;
+ }
+ fragment_queue_run (chn, msg_id, fragq, MSG_FRAG_STATE_DROP == fragq->state);
+ GNUNET_CONTAINER_heap_remove_root (chn->recv_msgs);
+ n++;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Removed %" PRIu64 " messages from queue.\n", chn, n);
+ return n;
+}
+
+
+/**
+ * Drop message queue of a channel.
+ *
+ * Remove all messages in queue without sending it to clients.
+ *
+ * @param chn Channel.
+ *
+ * @return Number of messages removed from queue.
+ */
+static uint64_t
+message_queue_drop (struct Channel *chn)
+{
+ uint64_t n = 0;
+ uint64_t msg_id;
+ while (GNUNET_YES == GNUNET_CONTAINER_heap_peek2 (chn->recv_msgs, NULL,
+ &msg_id))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "%p Dropping message %" PRIu64 " from queue.\n", chn, msg_id);
+ struct GNUNET_HashCode msg_id_hash;
+ hash_key_from_hll (&msg_id_hash, msg_id);
+
+ struct FragmentQueue *
+ fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &msg_id_hash);
+ GNUNET_assert (NULL != fragq);
+ fragment_queue_run (chn, msg_id, fragq, GNUNET_YES);
+ GNUNET_CONTAINER_heap_remove_root (chn->recv_msgs);
+ n++;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Removed %" PRIu64 " messages from queue.\n", chn, n);
+ return n;
+}
+
+
+/**
+ * Received result of GNUNET_PSYCSTORE_fragment_store().
+ */
+static void
+store_recv_fragment_store_result (void *cls, int64_t result,
+ const char *err_msg, uint16_t err_msg_size)
+{
+ struct Channel *chn = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p GNUNET_PSYCSTORE_fragment_store() returned %" PRId64 " (%.*s)\n",
+ chn, result, err_msg_size, err_msg);
+}
+
+
+/**
+ * Handle incoming message fragment from multicast.
+ *
+ * Store it using PSYCstore and send it to the clients of the channel in order.
+ */
+static void
+mcast_recv_message (void *cls, const struct GNUNET_MULTICAST_MessageHeader *mmsg)
+{
+ struct Channel *chn = cls;
+ uint16_t size = ntohs (mmsg->header.size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Received multicast message of size %u. "
+ "fragment_id=%" PRIu64 ", message_id=%" PRIu64
+ ", fragment_offset=%" PRIu64 ", flags=%" PRIu64 "\n",
+ chn, size,
+ GNUNET_ntohll (mmsg->fragment_id),
+ GNUNET_ntohll (mmsg->message_id),
+ GNUNET_ntohll (mmsg->fragment_offset),
+ GNUNET_ntohll (mmsg->flags));
+
+ GNUNET_PSYCSTORE_fragment_store (store, &chn->pub_key, mmsg, 0,
+ &store_recv_fragment_store_result, chn);
+
+ uint16_t first_ptype = 0, last_ptype = 0;
+ int check = GNUNET_PSYC_receive_check_parts (size - sizeof (*mmsg),
+ (const char *) &mmsg[1],
+ &first_ptype, &last_ptype);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Message check result %d, first part type %u, last part type %u\n",
+ chn, check, first_ptype, last_ptype);
+ if (GNUNET_SYSERR == check)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "%p Dropping incoming multicast message with invalid parts.\n",
+ chn);
+ GNUNET_break_op (0);
+ return;
+ }
+
+ fragment_queue_insert (chn, mmsg, first_ptype, last_ptype);
+ message_queue_run (chn);
+}
+
+
+/**
+ * Incoming request fragment from multicast for a master.
+ *
+ * @param cls Master.
+ * @param req The request.
+ */
+static void
+mcast_recv_request (void *cls,
+ const struct GNUNET_MULTICAST_RequestHeader *req)
+{
+ struct Master *mst = cls;
+ uint16_t size = ntohs (req->header.size);
+
+ char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&req->member_pub_key);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Received multicast request of size %u from %s.\n",
+ mst, size, str);
+ GNUNET_free (str);
+
+ uint16_t first_ptype = 0, last_ptype = 0;
+ if (GNUNET_SYSERR
+ == GNUNET_PSYC_receive_check_parts (size - sizeof (*req),
+ (const char *) &req[1],
+ &first_ptype, &last_ptype))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "%p Dropping incoming multicast request with invalid parts.\n",
+ mst);
+ GNUNET_break_op (0);
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Message parts: first: type %u, last: type %u\n",
+ first_ptype, last_ptype);
+
+ /* FIXME: in-order delivery */
+ client_send_mcast_req (mst, req);
+}
+
+
+/**
+ * Response from PSYCstore with the current counter values for a channel master.
+ */
+static void
+store_recv_master_counters (void *cls, int result, uint64_t max_fragment_id,
+ uint64_t max_message_id, uint64_t max_group_generation,
+ uint64_t max_state_message_id)
+{
+ struct Master *mst = cls;
+ struct Channel *chn = &mst->channel;
+ chn->store_op = NULL;
+
+ struct GNUNET_PSYC_CountersResultMessage res;
+ res.header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MASTER_START_ACK);
+ res.header.size = htons (sizeof (res));
+ res.result_code = htonl (result);
+ res.max_message_id = GNUNET_htonll (max_message_id);
+
+ if (GNUNET_OK == result || GNUNET_NO == result)
+ {
+ mst->max_message_id = max_message_id;
+ chn->max_message_id = max_message_id;
+ chn->max_state_message_id = max_state_message_id;
+ mst->max_group_generation = max_group_generation;
+ mst->origin
+ = GNUNET_MULTICAST_origin_start (cfg, &mst->priv_key, max_fragment_id,
+ mcast_recv_join_request,
+ mcast_recv_replay_fragment,
+ mcast_recv_replay_message,
+ mcast_recv_request,
+ mcast_recv_message, chn);
+ chn->is_ready = GNUNET_YES;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p GNUNET_PSYCSTORE_counters_get() "
+ "returned %d for channel %s.\n",
+ chn, result, GNUNET_h2s (&chn->pub_key_hash));
+ }
+
+ client_send_msg (chn, &res.header);
+}
+
+
+/**
+ * Response from PSYCstore with the current counter values for a channel slave.
+ */
+void
+store_recv_slave_counters (void *cls, int result, uint64_t max_fragment_id,
+ uint64_t max_message_id, uint64_t max_group_generation,
+ uint64_t max_state_message_id)
+{
+ struct Slave *slv = cls;
+ struct Channel *chn = &slv->channel;
+ chn->store_op = NULL;
+
+ struct GNUNET_PSYC_CountersResultMessage res;
+ res.header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN_ACK);
+ res.header.size = htons (sizeof (res));
+ res.result_code = htonl (result);
+ res.max_message_id = GNUNET_htonll (max_message_id);
+
+ if (GNUNET_YES == result || GNUNET_NO == result)
+ {
+ chn->max_message_id = max_message_id;
+ chn->max_state_message_id = max_state_message_id;
+ slv->member
+ = GNUNET_MULTICAST_member_join (cfg, &chn->pub_key, &slv->priv_key,
+ &slv->origin,
+ slv->relay_count, slv->relays,
+ &slv->join_msg->header,
+ mcast_recv_join_request,
+ mcast_recv_join_decision,
+ mcast_recv_replay_fragment,
+ mcast_recv_replay_message,
+ mcast_recv_message, chn);
+ if (NULL != slv->join_msg)
+ {
+ GNUNET_free (slv->join_msg);
+ slv->join_msg = NULL;
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p GNUNET_PSYCSTORE_counters_get() "
+ "returned %d for channel %s.\n",
+ chn, result, GNUNET_h2s (&chn->pub_key_hash));
+ }
+
+ client_send_msg (chn, &res.header);
+}
+
+
+static void
+channel_init (struct Channel *chn)
+{
+ chn->recv_msgs
+ = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
+ chn->recv_frags = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+}
+
+
+/**
+ * Handle a connecting client starting a channel master.
+ */
+static void
+handle_client_master_start (void *cls,
+ const struct MasterStartRequest *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+
+ struct GNUNET_CRYPTO_EddsaPublicKey pub_key;
+ struct GNUNET_HashCode pub_key_hash;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&req->channel_key, &pub_key);
+ GNUNET_CRYPTO_hash (&pub_key, sizeof (pub_key), &pub_key_hash);
+
+ struct Master *
+ mst = GNUNET_CONTAINER_multihashmap_get (masters, &pub_key_hash);
+ struct Channel *chn;
+
+ if (NULL == mst)
+ {
+ mst = GNUNET_malloc (sizeof (*mst));
+ mst->policy = ntohl (req->policy);
+ mst->priv_key = req->channel_key;
+ mst->join_reqs = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+
+ chn = c->channel = &mst->channel;
+ chn->master = mst;
+ chn->is_master = GNUNET_YES;
+ chn->pub_key = pub_key;
+ chn->pub_key_hash = pub_key_hash;
+ channel_init (chn);
+
+ GNUNET_CONTAINER_multihashmap_put (masters, &chn->pub_key_hash, chn,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ chn->store_op = GNUNET_PSYCSTORE_counters_get (store, &chn->pub_key,
+ store_recv_master_counters, mst);
+ }
+ else
+ {
+ chn = &mst->channel;
+
+ struct GNUNET_PSYC_CountersResultMessage *res;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYC_MASTER_START_ACK);
+ res->result_code = htonl (GNUNET_OK);
+ res->max_message_id = GNUNET_htonll (mst->max_message_id);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Client connected as master to channel %s.\n",
+ mst, GNUNET_h2s (&chn->pub_key_hash));
+
+ struct ClientList *cli = GNUNET_malloc (sizeof (*cli));
+ cli->client = client;
+ GNUNET_CONTAINER_DLL_insert (chn->clients_head, chn->clients_tail, cli);
+
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_slave_join (void *cls,
+ const struct SlaveJoinRequest *req)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle a connecting client joining as a channel slave.
+ */
+static void
+handle_client_slave_join (void *cls,
+ const struct SlaveJoinRequest *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+
+ uint16_t req_size = ntohs (req->header.size);
+
+ struct GNUNET_CRYPTO_EcdsaPublicKey slv_pub_key;
+ struct GNUNET_HashCode pub_key_hash, slv_pub_hash;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "got join request from client %p\n",
+ client);
+ GNUNET_CRYPTO_ecdsa_key_get_public (&req->slave_key, &slv_pub_key);
+ GNUNET_CRYPTO_hash (&slv_pub_key, sizeof (slv_pub_key), &slv_pub_hash);
+ GNUNET_CRYPTO_hash (&req->channel_pub_key, sizeof (req->channel_pub_key), &pub_key_hash);
+
+ struct GNUNET_CONTAINER_MultiHashMap *
+ chn_slv = GNUNET_CONTAINER_multihashmap_get (channel_slaves, &pub_key_hash);
+ struct Slave *slv = NULL;
+ struct Channel *chn;
+
+ if (NULL != chn_slv)
+ {
+ slv = GNUNET_CONTAINER_multihashmap_get (chn_slv, &slv_pub_hash);
+ }
+ if (NULL == slv)
+ {
+ slv = GNUNET_malloc (sizeof (*slv));
+ slv->priv_key = req->slave_key;
+ slv->pub_key = slv_pub_key;
+ slv->pub_key_hash = slv_pub_hash;
+ slv->origin = req->origin;
+ slv->relay_count = ntohl (req->relay_count);
+ slv->join_flags = ntohl (req->flags);
+
+ const struct GNUNET_PeerIdentity *
+ relays = (const struct GNUNET_PeerIdentity *) &req[1];
+ uint16_t relay_size = slv->relay_count * sizeof (*relays);
+ uint16_t join_msg_size = 0;
+
+ if (sizeof (*req) + relay_size + sizeof (struct GNUNET_MessageHeader)
+ <= req_size)
+ {
+ struct GNUNET_PSYC_Message *
+ join_msg = (struct GNUNET_PSYC_Message *) (((char *) &req[1]) + relay_size);
+ join_msg_size = ntohs (join_msg->header.size);
+ slv->join_msg = GNUNET_malloc (join_msg_size);
+ GNUNET_memcpy (slv->join_msg, join_msg, join_msg_size);
+ }
+ if (sizeof (*req) + relay_size + join_msg_size != req_size)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%u + %u + %u != %u\n",
+ (unsigned int) sizeof (*req),
+ relay_size,
+ join_msg_size,
+ req_size);
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ GNUNET_free (slv);
+ return;
+ }
+ if (0 < slv->relay_count)
+ {
+ slv->relays = GNUNET_malloc (relay_size);
+ GNUNET_memcpy (slv->relays, &req[1], relay_size);
+ }
+
+ chn = c->channel = &slv->channel;
+ chn->slave = slv;
+ chn->is_master = GNUNET_NO;
+ chn->pub_key = req->channel_pub_key;
+ chn->pub_key_hash = pub_key_hash;
+ channel_init (chn);
+
+ if (NULL == chn_slv)
+ {
+ chn_slv = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ GNUNET_CONTAINER_multihashmap_put (channel_slaves, &chn->pub_key_hash, chn_slv,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ }
+ GNUNET_CONTAINER_multihashmap_put (chn_slv, &slv->pub_key_hash, chn,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
+ GNUNET_CONTAINER_multihashmap_put (slaves, &chn->pub_key_hash, chn,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ chn->store_op = GNUNET_PSYCSTORE_counters_get (store, &chn->pub_key,
+ &store_recv_slave_counters, slv);
+ }
+ else
+ {
+ chn = &slv->channel;
+
+ struct GNUNET_PSYC_CountersResultMessage *res;
+
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN_ACK);
+ res->result_code = htonl (GNUNET_OK);
+ res->max_message_id = GNUNET_htonll (chn->max_message_id);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
+
+ if (GNUNET_PSYC_SLAVE_JOIN_LOCAL & slv->join_flags)
+ {
+ mcast_recv_join_decision (slv, GNUNET_YES,
+ NULL, 0, NULL, NULL);
+ }
+ else if (NULL == slv->member)
+ {
+ slv->member
+ = GNUNET_MULTICAST_member_join (cfg, &chn->pub_key, &slv->priv_key,
+ &slv->origin,
+ slv->relay_count, slv->relays,
+ &slv->join_msg->header,
+ &mcast_recv_join_request,
+ &mcast_recv_join_decision,
+ &mcast_recv_replay_fragment,
+ &mcast_recv_replay_message,
+ &mcast_recv_message, chn);
+ if (NULL != slv->join_msg)
+ {
+ GNUNET_free (slv->join_msg);
+ slv->join_msg = NULL;
+ }
+ }
+ else if (NULL != slv->join_dcsn)
+ {
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_copy (&slv->join_dcsn->header);
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Client %p connected as slave to channel %s.\n",
+ client,
+ GNUNET_h2s (&chn->pub_key_hash));
+
+ struct ClientList *cli = GNUNET_malloc (sizeof (*cli));
+ cli->client = client;
+ GNUNET_CONTAINER_DLL_insert (chn->clients_head, chn->clients_tail, cli);
+
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+struct JoinDecisionClosure
+{
+ int32_t is_admitted;
+ struct GNUNET_MessageHeader *msg;
+};
+
+
+/**
+ * Iterator callback for sending join decisions to multicast.
+ */
+static int
+mcast_send_join_decision (void *cls, const struct GNUNET_HashCode *pub_key_hash,
+ void *value)
+{
+ struct JoinDecisionClosure *jcls = cls;
+ struct GNUNET_MULTICAST_JoinHandle *jh = value;
+ // FIXME: add relays
+ GNUNET_MULTICAST_join_decision (jh, jcls->is_admitted, 0, NULL, jcls->msg);
+ return GNUNET_YES;
+}
+
+
+static int
+check_client_join_decision (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Join decision from client.
+ */
+static void
+handle_client_join_decision (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Channel *chn = c->channel;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_assert (GNUNET_YES == chn->is_master);
+ struct Master *mst = chn->master;
+
+ struct JoinDecisionClosure jcls;
+ jcls.is_admitted = ntohl (dcsn->is_admitted);
+ jcls.msg
+ = (sizeof (*dcsn) + sizeof (*jcls.msg) <= ntohs (dcsn->header.size))
+ ? (struct GNUNET_MessageHeader *) &dcsn[1]
+ : NULL;
+
+ struct GNUNET_HashCode slave_pub_hash;
+ GNUNET_CRYPTO_hash (&dcsn->slave_pub_key, sizeof (dcsn->slave_pub_key),
+ &slave_pub_hash);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Got join decision (%d) from client for channel %s..\n",
+ mst, jcls.is_admitted, GNUNET_h2s (&chn->pub_key_hash));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p ..and slave %s.\n",
+ mst, GNUNET_h2s (&slave_pub_hash));
+
+ GNUNET_CONTAINER_multihashmap_get_multiple (mst->join_reqs, &slave_pub_hash,
+ &mcast_send_join_decision, &jcls);
+ GNUNET_CONTAINER_multihashmap_remove_all (mst->join_reqs, &slave_pub_hash);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static void
+channel_part_cb (void *cls)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+ struct GNUNET_MQ_Envelope *env;
+
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_PART_ACK);
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
+ env);
+}
+
+
+static void
+handle_client_part_request (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct Client *c = cls;
+
+ c->channel->is_disconnecting = GNUNET_YES;
+ if (GNUNET_YES == c->channel->is_master)
+ {
+ struct Master *mst = (struct Master *) c->channel;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got part request from master %p\n",
+ mst);
+ GNUNET_assert (NULL != mst->origin);
+ GNUNET_MULTICAST_origin_stop (mst->origin, channel_part_cb, c->client);
+ }
+ else
+ {
+ struct Slave *slv = (struct Slave *) c->channel;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got part request from slave %p\n",
+ slv);
+ GNUNET_assert (NULL != slv->member);
+ GNUNET_MULTICAST_member_part (slv->member, channel_part_cb, c->client);
+ }
+ GNUNET_SERVICE_client_continue (c->client);
+}
+
+
+/**
+ * Send acknowledgement to a client.
+ *
+ * Sent after a message fragment has been passed on to multicast.
+ *
+ * @param chn The channel struct for the client.
+ */
+static void
+send_message_ack (struct Channel *chn, struct GNUNET_SERVICE_Client *client)
+{
+ struct GNUNET_MessageHeader *res;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK);
+
+ /* FIXME? */
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
+}
+
+
+/**
+ * Callback for the transmit functions of multicast.
+ */
+static int
+transmit_notify (void *cls, size_t *data_size, void *data)
+{
+ struct Channel *chn = cls;
+ struct TransmitMessage *tmit_msg = chn->tmit_head;
+
+ if (NULL == tmit_msg || *data_size < tmit_msg->size)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p transmit_notify: nothing to send.\n", chn);
+ if (NULL != tmit_msg && *data_size < tmit_msg->size)
+ GNUNET_break (0);
+ *data_size = 0;
+ return GNUNET_NO;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p transmit_notify: sending %u bytes.\n", chn, tmit_msg->size);
+
+ *data_size = tmit_msg->size;
+ GNUNET_memcpy (data, &tmit_msg[1], *data_size);
+
+ int ret
+ = (tmit_msg->last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END)
+ ? GNUNET_NO
+ : GNUNET_YES;
+
+ /* FIXME: handle disconnecting clients */
+ if (NULL != tmit_msg->client)
+ send_message_ack (chn, tmit_msg->client);
+
+ GNUNET_CONTAINER_DLL_remove (chn->tmit_head, chn->tmit_tail, tmit_msg);
+
+ if (NULL != chn->tmit_head)
+ {
+ GNUNET_SCHEDULER_add_now (&schedule_transmit_message, chn);
+ }
+ else if (GNUNET_YES == chn->is_disconnecting
+ && tmit_msg->last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END)
+ {
+ /* FIXME: handle partial message (when still in_transmit) */
+ GNUNET_free (tmit_msg);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (tmit_msg);
+ return ret;
+}
+
+
+/**
+ * Callback for the transmit functions of multicast.
+ */
+static int
+master_transmit_notify (void *cls, size_t *data_size, void *data)
+{
+ int ret = transmit_notify (cls, data_size, data);
+
+ if (GNUNET_YES == ret)
+ {
+ struct Master *mst = cls;
+ mst->tmit_handle = NULL;
+ }
+ return ret;
+}
+
+
+/**
+ * Callback for the transmit functions of multicast.
+ */
+static int
+slave_transmit_notify (void *cls, size_t *data_size, void *data)
+{
+ int ret = transmit_notify (cls, data_size, data);
+
+ if (GNUNET_YES == ret)
+ {
+ struct Slave *slv = cls;
+ slv->tmit_handle = NULL;
+ }
+ return ret;
+}
+
+
+/**
+ * Transmit a message from a channel master to the multicast group.
+ */
+static void
+master_transmit_message (struct Master *mst)
+{
+ struct Channel *chn = &mst->channel;
+ struct TransmitMessage *tmit_msg = chn->tmit_head;
+ if (NULL == tmit_msg)
+ return;
+ if (NULL == mst->tmit_handle)
+ {
+ mst->tmit_handle = GNUNET_MULTICAST_origin_to_all (mst->origin,
+ tmit_msg->id,
+ mst->max_group_generation,
+ &master_transmit_notify,
+ mst);
+ }
+ else
+ {
+ GNUNET_MULTICAST_origin_to_all_resume (mst->tmit_handle);
+ }
+}
+
+
+/**
+ * Transmit a message from a channel slave to the multicast group.
+ */
+static void
+slave_transmit_message (struct Slave *slv)
+{
+ if (NULL == slv->channel.tmit_head)
+ return;
+ if (NULL == slv->tmit_handle)
+ {
+ slv->tmit_handle = GNUNET_MULTICAST_member_to_origin (slv->member,
+ slv->channel.tmit_head->id,
+ &slave_transmit_notify,
+ slv);
+ }
+ else
+ {
+ GNUNET_MULTICAST_member_to_origin_resume (slv->tmit_handle);
+ }
+}
+
+
+static void
+transmit_message (struct Channel *chn)
+{
+ chn->is_master
+ ? master_transmit_message (chn->master)
+ : slave_transmit_message (chn->slave);
+}
+
+
+/**
+ * Queue a message from a channel master for sending to the multicast group.
+ */
+static void
+master_queue_message (struct Master *mst, struct TransmitMessage *tmit_msg)
+{
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == tmit_msg->first_ptype)
+ {
+ tmit_msg->id = ++mst->max_message_id;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p master_queue_message: message_id=%" PRIu64 "\n",
+ mst, tmit_msg->id);
+ struct GNUNET_PSYC_MessageMethod *pmeth
+ = (struct GNUNET_PSYC_MessageMethod *) &tmit_msg[1];
+
+ if (pmeth->flags & GNUNET_PSYC_MASTER_TRANSMIT_STATE_RESET)
+ {
+ pmeth->state_delta = GNUNET_htonll (GNUNET_PSYC_STATE_RESET);
+ }
+ else if (pmeth->flags & GNUNET_PSYC_MASTER_TRANSMIT_STATE_MODIFY)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p master_queue_message: state_delta=%" PRIu64 "\n",
+ mst, tmit_msg->id - mst->max_state_message_id);
+ pmeth->state_delta = GNUNET_htonll (tmit_msg->id
+ - mst->max_state_message_id);
+ mst->max_state_message_id = tmit_msg->id;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p master_queue_message: state not modified\n", mst);
+ pmeth->state_delta = GNUNET_htonll (GNUNET_PSYC_STATE_NOT_MODIFIED);
+ }
+
+ if (pmeth->flags & GNUNET_PSYC_MASTER_TRANSMIT_STATE_HASH)
+ {
+ /// @todo add state_hash to PSYC header
+ }
+ }
+}
+
+
+/**
+ * Queue a message from a channel slave for sending to the multicast group.
+ */
+static void
+slave_queue_message (struct Slave *slv, struct TransmitMessage *tmit_msg)
+{
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == tmit_msg->first_ptype)
+ {
+ struct GNUNET_PSYC_MessageMethod *pmeth
+ = (struct GNUNET_PSYC_MessageMethod *) &tmit_msg[1];
+ pmeth->state_delta = GNUNET_htonll (GNUNET_PSYC_STATE_NOT_MODIFIED);
+ tmit_msg->id = ++slv->max_request_id;
+ }
+}
+
+
+/**
+ * Queue PSYC message parts for sending to multicast.
+ *
+ * @param chn
+ * Channel to send to.
+ * @param client
+ * Client the message originates from.
+ * @param data_size
+ * Size of @a data.
+ * @param data
+ * Concatenated message parts.
+ * @param first_ptype
+ * First message part type in @a data.
+ * @param last_ptype
+ * Last message part type in @a data.
+ */
+static struct TransmitMessage *
+queue_message (struct Channel *chn,
+ struct GNUNET_SERVICE_Client *client,
+ size_t data_size,
+ const void *data,
+ uint16_t first_ptype, uint16_t last_ptype)
+{
+ struct TransmitMessage *
+ tmit_msg = GNUNET_malloc (sizeof (*tmit_msg) + data_size);
+ GNUNET_memcpy (&tmit_msg[1], data, data_size);
+ tmit_msg->client = client;
+ tmit_msg->size = data_size;
+ tmit_msg->first_ptype = first_ptype;
+ tmit_msg->last_ptype = last_ptype;
+
+ /* FIXME: separate queue per message ID */
+
+ GNUNET_CONTAINER_DLL_insert_tail (chn->tmit_head, chn->tmit_tail, tmit_msg);
+
+ chn->is_master
+ ? master_queue_message (chn->master, tmit_msg)
+ : slave_queue_message (chn->slave, tmit_msg);
+ return tmit_msg;
+}
+
+
+/**
+ * Cancel transmission of current message.
+ *
+ * @param chn Channel to send to.
+ * @param client Client the message originates from.
+ */
+static void
+transmit_cancel (struct Channel *chn, struct GNUNET_SERVICE_Client *client)
+{
+ uint16_t type = GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL;
+
+ struct GNUNET_MessageHeader msg;
+ msg.size = htons (sizeof (msg));
+ msg.type = htons (type);
+
+ queue_message (chn, client, sizeof (msg), &msg, type, type);
+ transmit_message (chn);
+
+ /* FIXME: cleanup */
+}
+
+
+static int
+check_client_psyc_message (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Incoming message from a master or slave client.
+ */
+static void
+handle_client_psyc_message (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Channel *chn = c->channel;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Received message from client.\n", chn);
+ GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, msg);
+
+ if (GNUNET_YES != chn->is_ready)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "%p Channel is not ready yet, disconnecting client %p.\n",
+ chn,
+ client);
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ uint16_t size = ntohs (msg->size);
+ if (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD < size - sizeof (*msg))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p Message payload too large: %u < %u.\n",
+ chn,
+ (unsigned int) GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD,
+ (unsigned int) (size - sizeof (*msg)));
+ GNUNET_break (0);
+ transmit_cancel (chn, client);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ uint16_t first_ptype = 0, last_ptype = 0;
+ if (GNUNET_SYSERR
+ == GNUNET_PSYC_receive_check_parts (size - sizeof (*msg),
+ (const char *) &msg[1],
+ &first_ptype, &last_ptype))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p Received invalid message part from client.\n", chn);
+ GNUNET_break (0);
+ transmit_cancel (chn, client);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Received message with first part type %u and last part type %u.\n",
+ chn, first_ptype, last_ptype);
+
+ queue_message (chn, client, size - sizeof (*msg), &msg[1],
+ first_ptype, last_ptype);
+ transmit_message (chn);
+ /* FIXME: send a few ACKs even before transmit_notify is called */
+
+ GNUNET_SERVICE_client_continue (client);
+};
+
+
+/**
+ * Received result of GNUNET_PSYCSTORE_membership_store()
+ */
+static void
+store_recv_membership_store_result (void *cls,
+ int64_t result,
+ const char *err_msg,
+ uint16_t err_msg_size)
+{
+ struct Operation *op = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p GNUNET_PSYCSTORE_membership_store() returned %" PRId64 " (%.*s)\n",
+ op->channel,
+ result,
+ (int) err_msg_size,
+ err_msg);
+
+ if (NULL != op->client)
+ client_send_result (op->client, op->op_id, result, err_msg, err_msg_size);
+ op_remove (op);
+}
+
+
+/**
+ * Client requests to add/remove a slave in the membership database.
+ */
+static void
+handle_client_membership_store (void *cls,
+ const struct ChannelMembershipStoreRequest *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Channel *chn = c->channel;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ struct Operation *op = op_add (chn, client, req->op_id, 0);
+
+ uint64_t announced_at = GNUNET_ntohll (req->announced_at);
+ uint64_t effective_since = GNUNET_ntohll (req->effective_since);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Received membership store request from client.\n", chn);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p did_join: %u, announced_at: %" PRIu64 ", effective_since: %" PRIu64 "\n",
+ chn, req->did_join, announced_at, effective_since);
+
+ GNUNET_PSYCSTORE_membership_store (store, &chn->pub_key, &req->slave_pub_key,
+ req->did_join, announced_at, effective_since,
+ 0, /* FIXME: group_generation */
+ &store_recv_membership_store_result, op);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+/**
+ * Received a fragment for GNUNET_PSYCSTORE_fragment_get(),
+ * in response to a history request from a client.
+ */
+static int
+store_recv_fragment_history (void *cls,
+ struct GNUNET_MULTICAST_MessageHeader *mmsg,
+ enum GNUNET_PSYCSTORE_MessageFlags flags)
+{
+ struct Operation *op = cls;
+ if (NULL == op->client)
+ { /* Requesting client already disconnected. */
+ return GNUNET_NO;
+ }
+ struct Channel *chn = op->channel;
+
+ struct GNUNET_PSYC_MessageHeader *pmsg;
+ uint16_t msize = ntohs (mmsg->header.size);
+ uint16_t psize = sizeof (*pmsg) + msize - sizeof (*mmsg);
+
+ struct GNUNET_OperationResultMessage *
+ res = GNUNET_malloc (sizeof (*res) + psize);
+ res->header.size = htons (sizeof (*res) + psize);
+ res->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT);
+ res->op_id = op->op_id;
+ res->result_code = GNUNET_htonll (GNUNET_OK);
+
+ pmsg = (struct GNUNET_PSYC_MessageHeader *) &res[1];
+ GNUNET_PSYC_message_header_init (pmsg, mmsg, flags | GNUNET_PSYC_MESSAGE_HISTORIC);
+ GNUNET_memcpy (&res[1], pmsg, psize);
+
+ /** @todo FIXME: send only to requesting client */
+ client_send_msg (chn, &res->header);
+
+ GNUNET_free (res);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Received the result of GNUNET_PSYCSTORE_fragment_get(),
+ * in response to a history request from a client.
+ */
+static void
+store_recv_fragment_history_result (void *cls, int64_t result,
+ const char *err_msg, uint16_t err_msg_size)
+{
+ struct Operation *op = cls;
+ if (NULL == op->client)
+ { /* Requesting client already disconnected. */
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p History replay #%" PRIu64 ": "
+ "PSYCSTORE returned %" PRId64 " (%.*s)\n",
+ op->channel, GNUNET_ntohll (op->op_id), result, err_msg_size, err_msg);
+
+ if (op->flags & GNUNET_PSYC_HISTORY_REPLAY_REMOTE)
+ {
+ /** @todo Multicast replay request for messages not found locally. */
+ }
+
+ client_send_result (op->client, op->op_id, result, err_msg, err_msg_size);
+ op_remove (op);
+}
+
+
+static int
+check_client_history_replay (void *cls,
+ const struct GNUNET_PSYC_HistoryRequestMessage *req)
+{
+ return GNUNET_OK;
+}
+
+
+/**
+ * Client requests channel history.
+ */
+static void
+handle_client_history_replay (void *cls,
+ const struct GNUNET_PSYC_HistoryRequestMessage *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Channel *chn = c->channel;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ uint16_t size = ntohs (req->header.size);
+ const char *method_prefix = (const char *) &req[1];
+
+ if (size < sizeof (*req) + 1
+ || '\0' != method_prefix[size - sizeof (*req) - 1])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%p History replay #%" PRIu64 ": "
+ "invalid method prefix. size: %u < %u?\n",
+ chn,
+ GNUNET_ntohll (req->op_id),
+ size,
+ (unsigned int) sizeof (*req) + 1);
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ struct Operation *op = op_add (chn, client, req->op_id, ntohl (req->flags));
+
+ if (0 == req->message_limit)
+ {
+ GNUNET_PSYCSTORE_message_get (store, &chn->pub_key, NULL,
+ GNUNET_ntohll (req->start_message_id),
+ GNUNET_ntohll (req->end_message_id),
+ 0, method_prefix,
+ &store_recv_fragment_history,
+ &store_recv_fragment_history_result, op);
+ }
+ else
+ {
+ GNUNET_PSYCSTORE_message_get_latest (store, &chn->pub_key, NULL,
+ GNUNET_ntohll (req->message_limit),
+ method_prefix,
+ &store_recv_fragment_history,
+ &store_recv_fragment_history_result,
+ op);
+ }
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+/**
+ * Received state var from PSYCstore, send it to client.
+ */
+static int
+store_recv_state_var (void *cls, const char *name,
+ const void *value, uint32_t value_size)
+{
+ struct Operation *op = cls;
+ struct GNUNET_OperationResultMessage *res;
+ struct GNUNET_MQ_Envelope *env;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p state_get #%" PRIu64 " - received var from PSYCstore: %s\n",
+ op->channel, GNUNET_ntohll (op->op_id), name);
+
+ if (NULL != name) /* First part */
+ {
+ uint16_t name_size = strnlen (name, GNUNET_PSYC_MODIFIER_MAX_PAYLOAD) + 1;
+ struct GNUNET_PSYC_MessageModifier *mod;
+ env = GNUNET_MQ_msg_extra (res,
+ sizeof (*mod) + name_size + value_size,
+ GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT);
+ res->op_id = op->op_id;
+
+ mod = (struct GNUNET_PSYC_MessageModifier *) &res[1];
+ mod->header.size = htons (sizeof (*mod) + name_size + value_size);
+ mod->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER);
+ mod->name_size = htons (name_size);
+ mod->value_size = htonl (value_size);
+ mod->oper = htons (GNUNET_PSYC_OP_ASSIGN);
+ GNUNET_memcpy (&mod[1], name, name_size);
+ GNUNET_memcpy (((char *) &mod[1]) + name_size, value, value_size);
+ }
+ else /* Continuation */
+ {
+ struct GNUNET_MessageHeader *mod;
+ env = GNUNET_MQ_msg_extra (res,
+ sizeof (*mod) + value_size,
+ GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT);
+ res->op_id = op->op_id;
+
+ mod = (struct GNUNET_MessageHeader *) &res[1];
+ mod->size = htons (sizeof (*mod) + value_size);
+ mod->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT);
+ GNUNET_memcpy (&mod[1], value, value_size);
+ }
+
+ // FIXME: client might have been disconnected
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (op->client), env);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Received result of GNUNET_PSYCSTORE_state_get()
+ * or GNUNET_PSYCSTORE_state_get_prefix()
+ */
+static void
+store_recv_state_result (void *cls, int64_t result,
+ const char *err_msg, uint16_t err_msg_size)
+{
+ struct Operation *op = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%p state_get #%" PRIu64 ": "
+ "PSYCSTORE returned %" PRId64 " (%.*s)\n",
+ op->channel, GNUNET_ntohll (op->op_id), result, err_msg_size, err_msg);
+
+ // FIXME: client might have been disconnected
+ client_send_result (op->client, op->op_id, result, err_msg, err_msg_size);
+ op_remove (op);
+}
+
+
+static int
+check_client_state_get (void *cls,
+ const struct StateRequest *req)
+{
+ struct Client *c = cls;
+ struct Channel *chn = c->channel;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ uint16_t name_size = ntohs (req->header.size) - sizeof (*req);
+ const char *name = (const char *) &req[1];
+ if (0 == name_size || '\0' != name[name_size - 1])
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Client requests best matching state variable from PSYCstore.
+ */
+static void
+handle_client_state_get (void *cls,
+ const struct StateRequest *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Channel *chn = c->channel;
+
+ const char *name = (const char *) &req[1];
+ struct Operation *op = op_add (chn, client, req->op_id, 0);
+ GNUNET_PSYCSTORE_state_get (store, &chn->pub_key, name,
+ &store_recv_state_var,
+ &store_recv_state_result, op);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_state_get_prefix (void *cls,
+ const struct StateRequest *req)
+{
+ struct Client *c = cls;
+ struct Channel *chn = c->channel;
+ if (NULL == chn)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ uint16_t name_size = ntohs (req->header.size) - sizeof (*req);
+ const char *name = (const char *) &req[1];
+ if (0 == name_size || '\0' != name[name_size - 1])
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Client requests state variables with a given prefix from PSYCstore.
+ */
+static void
+handle_client_state_get_prefix (void *cls,
+ const struct StateRequest *req)
+{
+ struct Client *c = cls;
+ struct GNUNET_SERVICE_Client *client = c->client;
+ struct Channel *chn = c->channel;
+
+ const char *name = (const char *) &req[1];
+ struct Operation *op = op_add (chn, client, req->op_id, 0);
+ GNUNET_PSYCSTORE_state_get_prefix (store, &chn->pub_key, name,
+ &store_recv_state_var,
+ &store_recv_state_result, op);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+/**
+ * Initialize the PSYC service.
+ *
+ * @param cls Closure.
+ * @param server The initialized server.
+ * @param c Configuration to use.
+ */
+static void
+run (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *c,
+ struct GNUNET_SERVICE_Handle *svc)
+{
+ cfg = c;
+ service = svc;
+ store = GNUNET_PSYCSTORE_connect (cfg);
+ stats = GNUNET_STATISTICS_create ("psyc", cfg);
+ masters = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ slaves = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES);
+ channel_slaves = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ recv_cache = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
+}
+
+
+/**
+ * Define "main" method using service macro.
+ */
+GNUNET_SERVICE_MAIN
+("psyc",
+ GNUNET_SERVICE_OPTION_NONE,
+ &run,
+ &client_notify_connect,
+ &client_notify_disconnect,
+ NULL,
+ GNUNET_MQ_hd_fixed_size (client_master_start,
+ GNUNET_MESSAGE_TYPE_PSYC_MASTER_START,
+ struct MasterStartRequest,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_slave_join,
+ GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN,
+ struct SlaveJoinRequest,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_join_decision,
+ GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION,
+ struct GNUNET_PSYC_JoinDecisionMessage,
+ NULL),
+ GNUNET_MQ_hd_fixed_size (client_part_request,
+ GNUNET_MESSAGE_TYPE_PSYC_PART_REQUEST,
+ struct GNUNET_MessageHeader,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_psyc_message,
+ GNUNET_MESSAGE_TYPE_PSYC_MESSAGE,
+ struct GNUNET_MessageHeader,
+ NULL),
+ GNUNET_MQ_hd_fixed_size (client_membership_store,
+ GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE,
+ struct ChannelMembershipStoreRequest,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_history_replay,
+ GNUNET_MESSAGE_TYPE_PSYC_HISTORY_REPLAY,
+ struct GNUNET_PSYC_HistoryRequestMessage,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_state_get,
+ GNUNET_MESSAGE_TYPE_PSYC_STATE_GET,
+ struct StateRequest,
+ NULL),
+ GNUNET_MQ_hd_var_size (client_state_get_prefix,
+ GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX,
+ struct StateRequest,
+ NULL));
+
+/* end of gnunet-service-psyc.c */
diff --git a/src/psyc/psyc.conf.in b/src/psyc/psyc.conf.in
new file mode 100644
index 0000000..764ccfa
--- /dev/null
+++ b/src/psyc/psyc.conf.in
@@ -0,0 +1,12 @@
+[psyc]
+START_ON_DEMAND = @START_ON_DEMAND@
+BINARY = gnunet-service-psyc
+
+UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-psyc.sock
+UNIX_MATCH_UID = YES
+UNIX_MATCH_GID = YES
+
+@UNIXONLY@PORT = 2115
+HOSTNAME = localhost
+ACCEPT_FROM = 127.0.0.1;
+ACCEPT_FROM6 = ::1;
diff --git a/src/psyc/psyc.h b/src/psyc/psyc.h
new file mode 100644
index 0000000..74bbf3e
--- /dev/null
+++ b/src/psyc/psyc.h
@@ -0,0 +1,178 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/psyc.h
+ * @brief Common type definitions for the PSYC service and API.
+ * @author Gabor X Toth
+ */
+
+#ifndef PSYC_H
+#define PSYC_H
+
+#include "platform.h"
+#include "gnunet_psyc_service.h"
+
+
+int
+GNUNET_PSYC_check_message_parts (uint16_t data_size, const char *data,
+ uint16_t *first_ptype, uint16_t *last_ptype);
+
+void
+GNUNET_PSYC_log_message (enum GNUNET_ErrorType kind,
+ const struct GNUNET_MessageHeader *msg);
+
+
+enum MessageState
+{
+ MSG_STATE_START = 0,
+ MSG_STATE_HEADER = 1,
+ MSG_STATE_METHOD = 2,
+ MSG_STATE_MODIFIER = 3,
+ MSG_STATE_MOD_CONT = 4,
+ MSG_STATE_DATA = 5,
+ MSG_STATE_END = 6,
+ MSG_STATE_CANCEL = 7,
+ MSG_STATE_ERROR = 8,
+};
+
+
+enum MessageFragmentState
+{
+ MSG_FRAG_STATE_START = 0,
+ MSG_FRAG_STATE_HEADER = 1,
+ MSG_FRAG_STATE_DATA = 2,
+ MSG_FRAG_STATE_END = 3,
+ MSG_FRAG_STATE_CANCEL = 4,
+ MSG_FRAG_STATE_DROP = 5,
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**** library -> service ****/
+
+
+struct MasterStartRequest
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_MASTER_START
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t policy GNUNET_PACKED;
+
+ struct GNUNET_CRYPTO_EddsaPrivateKey channel_key;
+};
+
+
+struct SlaveJoinRequest
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t relay_count GNUNET_PACKED;
+
+ struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key;
+
+ struct GNUNET_CRYPTO_EcdsaPrivateKey slave_key;
+
+ struct GNUNET_PeerIdentity origin;
+
+ uint32_t flags GNUNET_PACKED;
+
+ /* Followed by struct GNUNET_PeerIdentity relays[relay_count] */
+
+ /* Followed by struct GNUNET_MessageHeader join_msg */
+};
+
+
+struct ChannelMembershipStoreRequest
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t reserved GNUNET_PACKED;
+
+ uint64_t op_id GNUNET_PACKED;
+
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+
+ uint64_t announced_at GNUNET_PACKED;
+
+ uint64_t effective_since GNUNET_PACKED;
+
+ uint8_t did_join;
+};
+
+
+struct HistoryRequest
+{
+ /**
+ * Type: GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_HISTORY_REQUEST
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t reserved GNUNET_PACKED;
+
+ /**
+ * ID for this operation.
+ */
+ uint64_t op_id GNUNET_PACKED;
+
+ uint64_t start_message_id GNUNET_PACKED;
+
+ uint64_t end_message_id GNUNET_PACKED;
+
+ uint64_t message_limit GNUNET_PACKED;
+};
+
+
+struct StateRequest
+{
+ /**
+ * Types:
+ * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET
+ * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET_PREFIX
+ */
+ struct GNUNET_MessageHeader header;
+
+ uint32_t reserved GNUNET_PACKED;
+
+ /**
+ * ID for this operation.
+ */
+ uint64_t op_id GNUNET_PACKED;
+
+ /* Followed by NUL-terminated name. */
+};
+
+
+/**** service -> library ****/
+
+
+GNUNET_NETWORK_STRUCT_END
+
+#endif
diff --git a/src/psyc/psyc_api.c b/src/psyc/psyc_api.c
new file mode 100644
index 0000000..37ea112
--- /dev/null
+++ b/src/psyc/psyc_api.c
@@ -0,0 +1,1584 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/psyc_api.c
+ * @brief PSYC service; high-level access to the PSYC protocol
+ * note that clients of this API are NOT expected to
+ * understand the PSYC message format, only the semantics!
+ * Parsing (and serializing) the PSYC stream format is done
+ * within the implementation of the libgnunetpsyc library,
+ * and this API deliberately exposes as little as possible
+ * of the actual data stream format to the application!
+ * @author Gabor X Toth
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_multicast_service.h"
+#include "gnunet_psyc_service.h"
+#include "gnunet_psyc_util_lib.h"
+#include "psyc.h"
+
+#define LOG(kind,...) GNUNET_log_from (kind, "psyc-api",__VA_ARGS__)
+
+
+/**
+ * Handle to access PSYC channel operations for both the master and slaves.
+ */
+struct GNUNET_PSYC_Channel
+{
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Client connection to the service.
+ */
+ struct GNUNET_MQ_Handle *mq;
+
+ /**
+ * Message to send on connect.
+ */
+ struct GNUNET_MQ_Envelope *connect_env;
+
+ /**
+ * Time to wait until we try to reconnect on failure.
+ */
+ struct GNUNET_TIME_Relative reconnect_delay;
+
+ /**
+ * Task for reconnecting when the listener fails.
+ */
+ struct GNUNET_SCHEDULER_Task *reconnect_task;
+
+ /**
+ * Async operations.
+ */
+ struct GNUNET_OP_Handle *op;
+
+ /**
+ * Transmission handle;
+ */
+ struct GNUNET_PSYC_TransmitHandle *tmit;
+
+ /**
+ * Receipt handle;
+ */
+ struct GNUNET_PSYC_ReceiveHandle *recv;
+
+ /**
+ * Function called after disconnected from the service.
+ */
+ GNUNET_ContinuationCallback disconnect_cb;
+
+ /**
+ * Closure for @a disconnect_cb.
+ */
+ void *disconnect_cls;
+
+ /**
+ * Are we polling for incoming messages right now?
+ */
+ uint8_t in_receive;
+
+ /**
+ * Is this a master or slave channel?
+ */
+ uint8_t is_master;
+
+ /**
+ * Is this channel in the process of disconnecting from the service?
+ * #GNUNET_YES or #GNUNET_NO
+ */
+ uint8_t is_disconnecting;
+};
+
+
+/**
+ * Handle for the master of a PSYC channel.
+ */
+struct GNUNET_PSYC_Master
+{
+ struct GNUNET_PSYC_Channel chn;
+
+ GNUNET_PSYC_MasterStartCallback start_cb;
+
+ /**
+ * Join request callback.
+ */
+ GNUNET_PSYC_JoinRequestCallback join_req_cb;
+
+ /**
+ * Closure for the callbacks.
+ */
+ void *cb_cls;
+};
+
+
+/**
+ * Handle for a PSYC channel slave.
+ */
+struct GNUNET_PSYC_Slave
+{
+ struct GNUNET_PSYC_Channel chn;
+
+ GNUNET_PSYC_SlaveConnectCallback connect_cb;
+
+ GNUNET_PSYC_JoinDecisionCallback join_dcsn_cb;
+
+ /**
+ * Closure for the callbacks.
+ */
+ void *cb_cls;
+};
+
+
+/**
+ * Handle that identifies a join request.
+ *
+ * Used to match calls to #GNUNET_PSYC_JoinRequestCallback to the
+ * corresponding calls to GNUNET_PSYC_join_decision().
+ */
+struct GNUNET_PSYC_JoinHandle
+{
+ struct GNUNET_PSYC_Master *mst;
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+};
+
+
+/**
+ * Handle for a pending PSYC transmission operation.
+ */
+struct GNUNET_PSYC_SlaveTransmitHandle
+{
+
+};
+
+
+struct GNUNET_PSYC_HistoryRequest
+{
+ /**
+ * Channel.
+ */
+ struct GNUNET_PSYC_Channel *chn;
+
+ /**
+ * Operation ID.
+ */
+ uint64_t op_id;
+
+ /**
+ * Message handler.
+ */
+ struct GNUNET_PSYC_ReceiveHandle *recv;
+
+ /**
+ * Function to call when the operation finished.
+ */
+ GNUNET_ResultCallback result_cb;
+
+ /**
+ * Closure for @a result_cb.
+ */
+ void *cls;
+};
+
+
+struct GNUNET_PSYC_StateRequest
+{
+ /**
+ * Channel.
+ */
+ struct GNUNET_PSYC_Channel *chn;
+
+ /**
+ * Operation ID.
+ */
+ uint64_t op_id;
+
+ /**
+ * State variable result callback.
+ */
+ GNUNET_PSYC_StateVarCallback var_cb;
+
+ /**
+ * Function to call when the operation finished.
+ */
+ GNUNET_ResultCallback result_cb;
+
+ /**
+ * Closure for @a result_cb.
+ */
+ void *cls;
+};
+
+
+static int
+check_channel_result (void *cls,
+ const struct GNUNET_OperationResultMessage *res)
+{
+ return GNUNET_OK;
+}
+
+
+static void
+handle_channel_result (void *cls,
+ const struct GNUNET_OperationResultMessage *res)
+{
+ struct GNUNET_PSYC_Channel *chn = cls;
+
+ uint16_t size = ntohs (res->header.size);
+ if (size < sizeof (*res))
+ { /* Error, message too small. */
+ GNUNET_break (0);
+ return;
+ }
+
+ uint16_t data_size = size - sizeof (*res);
+ const char *data = (0 < data_size) ? (void *) &res[1] : NULL;
+ GNUNET_OP_result (chn->op, GNUNET_ntohll (res->op_id),
+ GNUNET_ntohll (res->result_code),
+ data, data_size, NULL);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "handle_channel_result: Received result message with OP ID %" PRIu64 "\n",
+ GNUNET_ntohll (res->op_id));
+}
+
+
+static void
+op_recv_history_result (void *cls, int64_t result,
+ const void *data, uint16_t data_size)
+{
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Received history replay result: %" PRId64 ".\n", result);
+
+ struct GNUNET_PSYC_HistoryRequest *hist = cls;
+
+ if (NULL != hist->result_cb)
+ hist->result_cb (hist->cls, result, data, data_size);
+
+ GNUNET_PSYC_receive_destroy (hist->recv);
+ GNUNET_free (hist);
+}
+
+
+static void
+op_recv_state_result (void *cls, int64_t result,
+ const void *data, uint16_t data_size)
+{
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Received state request result: %" PRId64 ".\n", result);
+
+ struct GNUNET_PSYC_StateRequest *sr = cls;
+
+ if (NULL != sr->result_cb)
+ sr->result_cb (sr->cls, result, data, data_size);
+
+ GNUNET_free (sr);
+}
+
+
+static int
+check_channel_history_result (void *cls,
+ const struct GNUNET_OperationResultMessage *res)
+{
+ struct GNUNET_PSYC_MessageHeader *
+ pmsg = (struct GNUNET_PSYC_MessageHeader *) GNUNET_MQ_extract_nested_mh (res);
+ uint16_t size = ntohs (res->header.size);
+
+ if ( (NULL == pmsg) ||
+ (size < sizeof (*res) + sizeof (*pmsg)) )
+ { /* Error, message too small. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+static void
+handle_channel_history_result (void *cls,
+ const struct GNUNET_OperationResultMessage *res)
+{
+ struct GNUNET_PSYC_Channel *chn = cls;
+ struct GNUNET_PSYC_MessageHeader *
+ pmsg = (struct GNUNET_PSYC_MessageHeader *) GNUNET_MQ_extract_nested_mh (res);
+ GNUNET_ResultCallback result_cb = NULL;
+ struct GNUNET_PSYC_HistoryRequest *hist = NULL;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "%p Received historic fragment for message #%" PRIu64 ".\n",
+ chn,
+ GNUNET_ntohll (pmsg->message_id));
+
+ if (GNUNET_YES != GNUNET_OP_get (chn->op,
+ GNUNET_ntohll (res->op_id),
+ &result_cb, (void *) &hist, NULL))
+ { /* Operation not found. */
+ LOG (GNUNET_ERROR_TYPE_WARNING,
+ "%p Replay operation not found for historic fragment of message #%"
+ PRIu64 ".\n",
+ chn, GNUNET_ntohll (pmsg->message_id));
+ return;
+ }
+
+ GNUNET_PSYC_receive_message (hist->recv,
+ (const struct GNUNET_PSYC_MessageHeader *) pmsg);
+}
+
+
+static int
+check_channel_state_result (void *cls,
+ const struct GNUNET_OperationResultMessage *res)
+{
+ const struct GNUNET_MessageHeader *mod = GNUNET_MQ_extract_nested_mh (res);
+ uint16_t mod_size;
+ uint16_t size;
+
+ if (NULL == mod)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ mod_size = ntohs (mod->size);
+ size = ntohs (res->header.size);
+ if (size - sizeof (*res) != mod_size)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+static void
+handle_channel_state_result (void *cls,
+ const struct GNUNET_OperationResultMessage *res)
+{
+ struct GNUNET_PSYC_Channel *chn = cls;
+
+ GNUNET_ResultCallback result_cb = NULL;
+ struct GNUNET_PSYC_StateRequest *sr = NULL;
+
+ if (GNUNET_YES != GNUNET_OP_get (chn->op,
+ GNUNET_ntohll (res->op_id),
+ &result_cb, (void *) &sr, NULL))
+ { /* Operation not found. */
+ return;
+ }
+
+ const struct GNUNET_MessageHeader *mod = GNUNET_MQ_extract_nested_mh (res);
+ if (NULL == mod)
+ {
+ GNUNET_break_op (0);
+ return;
+ }
+ uint16_t mod_size = ntohs (mod->size);
+
+ switch (ntohs (mod->type))
+ {
+ case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER:
+ {
+ const struct GNUNET_PSYC_MessageModifier *
+ pmod = (const struct GNUNET_PSYC_MessageModifier *) mod;
+
+ const char *name = (const char *) &pmod[1];
+ uint16_t name_size = ntohs (pmod->name_size);
+ if (0 == name_size
+ || mod_size - sizeof (*pmod) < name_size
+ || '\0' != name[name_size - 1])
+ {
+ GNUNET_break_op (0);
+ return;
+ }
+ sr->var_cb (sr->cls, mod, name, name + name_size,
+ ntohs (pmod->header.size) - sizeof (*pmod),
+ ntohs (pmod->value_size));
+ break;
+ }
+
+ case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT:
+ sr->var_cb (sr->cls, mod, NULL, (const char *) &mod[1],
+ mod_size - sizeof (*mod), 0);
+ break;
+ }
+}
+
+
+static int
+check_channel_message (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *pmsg)
+{
+ return GNUNET_OK;
+}
+
+
+static void
+handle_channel_message (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *pmsg)
+{
+ struct GNUNET_PSYC_Channel *chn = cls;
+
+ GNUNET_PSYC_receive_message (chn->recv, pmsg);
+}
+
+
+static void
+handle_channel_message_ack (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct GNUNET_PSYC_Channel *chn = cls;
+
+ GNUNET_PSYC_transmit_got_ack (chn->tmit);
+}
+
+
+static void
+handle_master_start_ack (void *cls,
+ const struct GNUNET_PSYC_CountersResultMessage *cres)
+{
+ struct GNUNET_PSYC_Master *mst = cls;
+
+ int32_t result = ntohl (cres->result_code);
+ if (GNUNET_OK != result && GNUNET_NO != result)
+ {
+ LOG (GNUNET_ERROR_TYPE_ERROR, "Could not start master: %ld\n", result);
+ GNUNET_break (0);
+ /* FIXME: disconnect */
+ }
+ if (NULL != mst->start_cb)
+ mst->start_cb (mst->cb_cls, result, GNUNET_ntohll (cres->max_message_id));
+}
+
+
+static int
+check_master_join_request (void *cls,
+ const struct GNUNET_PSYC_JoinRequestMessage *req)
+{
+ if ( ((sizeof (*req) + sizeof (struct GNUNET_PSYC_Message)) <= ntohs (req->header.size)) &&
+ (NULL == GNUNET_MQ_extract_nested_mh (req)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+static void
+handle_master_join_request (void *cls,
+ const struct GNUNET_PSYC_JoinRequestMessage *req)
+{
+ struct GNUNET_PSYC_Master *mst = cls;
+
+ if (NULL == mst->join_req_cb)
+ return;
+
+ const struct GNUNET_PSYC_Message *join_msg = NULL;
+ if (sizeof (*req) + sizeof (*join_msg) <= ntohs (req->header.size))
+ {
+ join_msg = (struct GNUNET_PSYC_Message *) GNUNET_MQ_extract_nested_mh (req);
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Received join_msg of type %u and size %u.\n",
+ ntohs (join_msg->header.type),
+ ntohs (join_msg->header.size));
+ }
+
+ struct GNUNET_PSYC_JoinHandle *jh = GNUNET_malloc (sizeof (*jh));
+ jh->mst = mst;
+ jh->slave_pub_key = req->slave_pub_key;
+
+ if (NULL != mst->join_req_cb)
+ mst->join_req_cb (mst->cb_cls, req, &req->slave_pub_key, join_msg, jh);
+}
+
+
+static void
+handle_slave_join_ack (void *cls,
+ const struct GNUNET_PSYC_CountersResultMessage *cres)
+{
+ struct GNUNET_PSYC_Slave *slv = cls;
+
+ int32_t result = ntohl (cres->result_code);
+ if (GNUNET_YES != result && GNUNET_NO != result)
+ {
+ LOG (GNUNET_ERROR_TYPE_ERROR, "Could not join slave.\n");
+ GNUNET_break (0);
+ /* FIXME: disconnect */
+ }
+ if (NULL != slv->connect_cb)
+ slv->connect_cb (slv->cb_cls, result, GNUNET_ntohll (cres->max_message_id));
+}
+
+
+static int
+check_slave_join_decision (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn)
+{
+ return GNUNET_OK;
+}
+
+
+static void
+handle_slave_join_decision (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn)
+{
+ struct GNUNET_PSYC_Slave *slv = cls;
+
+ struct GNUNET_PSYC_Message *pmsg = NULL;
+ if (ntohs (dcsn->header.size) <= sizeof (*dcsn) + sizeof (*pmsg))
+ pmsg = (struct GNUNET_PSYC_Message *) &dcsn[1];
+
+ if (NULL != slv->join_dcsn_cb)
+ slv->join_dcsn_cb (slv->cb_cls, dcsn, ntohl (dcsn->is_admitted), pmsg);
+}
+
+
+static void
+channel_cleanup (struct GNUNET_PSYC_Channel *chn)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "cleaning up channel %p\n",
+ chn);
+ if (NULL != chn->tmit)
+ {
+ GNUNET_PSYC_transmit_destroy (chn->tmit);
+ chn->tmit = NULL;
+ }
+ if (NULL != chn->recv)
+ {
+
+ GNUNET_PSYC_receive_destroy (chn->recv);
+ chn->recv = NULL;
+ }
+ if (NULL != chn->connect_env)
+ {
+ GNUNET_MQ_discard (chn->connect_env);
+ chn->connect_env = NULL;
+ }
+ if (NULL != chn->mq)
+ {
+ GNUNET_MQ_destroy (chn->mq);
+ chn->mq = NULL;
+ }
+ if (NULL != chn->disconnect_cb)
+ {
+ chn->disconnect_cb (chn->disconnect_cls);
+ chn->disconnect_cb = NULL;
+ }
+ GNUNET_free (chn);
+}
+
+
+static void
+handle_channel_part_ack (void *cls,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct GNUNET_PSYC_Channel *chn = cls;
+
+ channel_cleanup (chn);
+}
+
+
+/*** MASTER ***/
+
+
+static void
+master_connect (struct GNUNET_PSYC_Master *mst);
+
+
+static void
+master_reconnect (void *cls)
+{
+ master_connect (cls);
+}
+
+
+/**
+ * Master client disconnected from service.
+ *
+ * Reconnect after backoff period.
+ */
+static void
+master_disconnected (void *cls, enum GNUNET_MQ_Error error)
+{
+ struct GNUNET_PSYC_Master *mst = cls;
+ struct GNUNET_PSYC_Channel *chn = &mst->chn;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Master client disconnected (%d), re-connecting\n",
+ (int) error);
+ if (NULL != chn->tmit)
+ {
+ GNUNET_PSYC_transmit_destroy (chn->tmit);
+ chn->tmit = NULL;
+ }
+ if (NULL != chn->mq)
+ {
+ GNUNET_MQ_destroy (chn->mq);
+ chn->mq = NULL;
+ }
+ chn->reconnect_task = GNUNET_SCHEDULER_add_delayed (chn->reconnect_delay,
+ master_reconnect,
+ mst);
+ chn->reconnect_delay = GNUNET_TIME_STD_BACKOFF (chn->reconnect_delay);
+}
+
+
+static void
+master_connect (struct GNUNET_PSYC_Master *mst)
+{
+ struct GNUNET_PSYC_Channel *chn = &mst->chn;
+
+ struct GNUNET_MQ_MessageHandler handlers[] = {
+ GNUNET_MQ_hd_fixed_size (master_start_ack,
+ GNUNET_MESSAGE_TYPE_PSYC_MASTER_START_ACK,
+ struct GNUNET_PSYC_CountersResultMessage,
+ mst),
+ GNUNET_MQ_hd_var_size (master_join_request,
+ GNUNET_MESSAGE_TYPE_PSYC_JOIN_REQUEST,
+ struct GNUNET_PSYC_JoinRequestMessage,
+ mst),
+ GNUNET_MQ_hd_fixed_size (channel_part_ack,
+ GNUNET_MESSAGE_TYPE_PSYC_PART_ACK,
+ struct GNUNET_MessageHeader,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_message,
+ GNUNET_MESSAGE_TYPE_PSYC_MESSAGE,
+ struct GNUNET_PSYC_MessageHeader,
+ chn),
+ GNUNET_MQ_hd_fixed_size (channel_message_ack,
+ GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK,
+ struct GNUNET_MessageHeader,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_history_result,
+ GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT,
+ struct GNUNET_OperationResultMessage,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_state_result,
+ GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT,
+ struct GNUNET_OperationResultMessage,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_result,
+ GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE,
+ struct GNUNET_OperationResultMessage,
+ chn),
+ GNUNET_MQ_handler_end ()
+ };
+
+ chn->mq = GNUNET_CLIENT_connect (chn->cfg,
+ "psyc",
+ handlers,
+ &master_disconnected,
+ mst);
+ GNUNET_assert (NULL != chn->mq);
+ chn->tmit = GNUNET_PSYC_transmit_create (chn->mq);
+
+ GNUNET_MQ_send_copy (chn->mq, chn->connect_env);
+}
+
+
+/**
+ * Start a PSYC master channel.
+ *
+ * Will start a multicast group identified by the given ECC key. Messages
+ * received from group members will be given to the respective handler methods.
+ * If a new member wants to join a group, the "join" method handler will be
+ * invoked; the join handler must then generate a "join" message to approve the
+ * joining of the new member. The channel can also change group membership
+ * without explicit requests. Note that PSYC doesn't itself "understand" join
+ * or part messages, the respective methods must call other PSYC functions to
+ * inform PSYC about the meaning of the respective events.
+ *
+ * @param cfg Configuration to use (to connect to PSYC service).
+ * @param channel_key ECC key that will be used to sign messages for this
+ * PSYC session. The public key is used to identify the PSYC channel.
+ * Note that end-users will usually not use the private key directly, but
+ * rather look it up in GNS for places managed by other users, or select
+ * a file with the private key(s) when setting up their own channels
+ * FIXME: we'll likely want to use NOT the p521 curve here, but a cheaper
+ * one in the future.
+ * @param policy Channel policy specifying join and history restrictions.
+ * Used to automate join decisions.
+ * @param message_cb Function to invoke on message parts received from slaves.
+ * @param join_request_cb Function to invoke when a slave wants to join.
+ * @param master_start_cb Function to invoke after the channel master started.
+ * @param cls Closure for @a method and @a join_cb.
+ *
+ * @return Handle for the channel master, NULL on error.
+ */
+struct GNUNET_PSYC_Master *
+GNUNET_PSYC_master_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key,
+ enum GNUNET_PSYC_Policy policy,
+ GNUNET_PSYC_MasterStartCallback start_cb,
+ GNUNET_PSYC_JoinRequestCallback join_request_cb,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ void *cls)
+{
+ struct GNUNET_PSYC_Master *mst = GNUNET_new (struct GNUNET_PSYC_Master);
+ struct GNUNET_PSYC_Channel *chn = &mst->chn;
+ struct MasterStartRequest *req;
+
+ chn->connect_env = GNUNET_MQ_msg (req,
+ GNUNET_MESSAGE_TYPE_PSYC_MASTER_START);
+ req->channel_key = *channel_key;
+ req->policy = policy;
+
+ chn->cfg = cfg;
+ chn->is_master = GNUNET_YES;
+ chn->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+
+ chn->op = GNUNET_OP_create ();
+ chn->recv = GNUNET_PSYC_receive_create (message_cb, message_part_cb, cls);
+
+ mst->start_cb = start_cb;
+ mst->join_req_cb = join_request_cb;
+ mst->cb_cls = cls;
+
+ master_connect (mst);
+ return mst;
+}
+
+
+/**
+ * Stop a PSYC master channel.
+ *
+ * @param master PSYC channel master to stop.
+ * @param keep_active FIXME
+ */
+void
+GNUNET_PSYC_master_stop (struct GNUNET_PSYC_Master *mst,
+ int keep_active,
+ GNUNET_ContinuationCallback stop_cb,
+ void *stop_cls)
+{
+ struct GNUNET_PSYC_Channel *chn = &mst->chn;
+ struct GNUNET_MQ_Envelope *env;
+
+ chn->is_disconnecting = GNUNET_YES;
+ chn->disconnect_cb = stop_cb;
+ chn->disconnect_cls = stop_cls;
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_PART_REQUEST);
+ GNUNET_MQ_send (chn->mq, env);
+}
+
+
+/**
+ * Function to call with the decision made for a join request.
+ *
+ * Must be called once and only once in response to an invocation of the
+ * #GNUNET_PSYC_JoinCallback.
+ *
+ * @param jh Join request handle.
+ * @param is_admitted #GNUNET_YES if the join is approved,
+ * #GNUNET_NO if it is disapproved,
+ * #GNUNET_SYSERR if we cannot answer the request.
+ * @param relay_count Number of relays given.
+ * @param relays Array of suggested peers that might be useful relays to use
+ * when joining the multicast group (essentially a list of peers that
+ * are already part of the multicast group and might thus be willing
+ * to help with routing). If empty, only this local peer (which must
+ * be the multicast origin) is a good candidate for building the
+ * multicast tree. Note that it is unnecessary to specify our own
+ * peer identity in this array.
+ * @param join_resp Application-dependent join response message.
+ *
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR if the message is too large.
+ */
+int
+GNUNET_PSYC_join_decision (struct GNUNET_PSYC_JoinHandle *jh,
+ int is_admitted,
+ uint32_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ const struct GNUNET_PSYC_Message *join_resp)
+{
+ struct GNUNET_PSYC_Channel *chn = &jh->mst->chn;
+ struct GNUNET_PSYC_JoinDecisionMessage *dcsn;
+ uint16_t join_resp_size
+ = (NULL != join_resp) ? ntohs (join_resp->header.size) : 0;
+ uint16_t relay_size = relay_count * sizeof (*relays);
+
+ if (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD
+ < sizeof (*dcsn) + relay_size + join_resp_size)
+ return GNUNET_SYSERR;
+
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (dcsn, relay_size + join_resp_size,
+ GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION);
+ dcsn->is_admitted = htonl (is_admitted);
+ dcsn->slave_pub_key = jh->slave_pub_key;
+
+ if (0 < join_resp_size)
+ GNUNET_memcpy (&dcsn[1], join_resp, join_resp_size);
+
+ GNUNET_MQ_send (chn->mq, env);
+ GNUNET_free (jh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Send a message to call a method to all members in the PSYC channel.
+ *
+ * @param master Handle to the PSYC channel.
+ * @param method_name Which method should be invoked.
+ * @param notify_mod Function to call to obtain modifiers.
+ * @param notify_data Function to call to obtain fragments of the data.
+ * @param notify_cls Closure for @a notify_mod and @a notify_data.
+ * @param flags Flags for the message being transmitted.
+ *
+ * @return Transmission handle, NULL on error (i.e. more than one request queued).
+ */
+struct GNUNET_PSYC_MasterTransmitHandle *
+GNUNET_PSYC_master_transmit (struct GNUNET_PSYC_Master *mst,
+ const char *method_name,
+ GNUNET_PSYC_TransmitNotifyModifier notify_mod,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_cls,
+ enum GNUNET_PSYC_MasterTransmitFlags flags)
+{
+ if (GNUNET_OK
+ == GNUNET_PSYC_transmit_message (mst->chn.tmit, method_name, NULL,
+ notify_mod, notify_data, notify_cls,
+ flags))
+ return (struct GNUNET_PSYC_MasterTransmitHandle *) mst->chn.tmit;
+ else
+ return NULL;
+}
+
+
+/**
+ * Resume transmission to the channel.
+ *
+ * @param tmit Handle of the request that is being resumed.
+ */
+void
+GNUNET_PSYC_master_transmit_resume (struct GNUNET_PSYC_MasterTransmitHandle *tmit)
+{
+ GNUNET_PSYC_transmit_resume ((struct GNUNET_PSYC_TransmitHandle *) tmit);
+}
+
+
+/**
+ * Abort transmission request to the channel.
+ *
+ * @param tmit Handle of the request that is being aborted.
+ */
+void
+GNUNET_PSYC_master_transmit_cancel (struct GNUNET_PSYC_MasterTransmitHandle *tmit)
+{
+ GNUNET_PSYC_transmit_cancel ((struct GNUNET_PSYC_TransmitHandle *) tmit);
+}
+
+
+/**
+ * Convert a channel @a master to a @e channel handle to access the @e channel
+ * APIs.
+ *
+ * @param master Channel master handle.
+ *
+ * @return Channel handle, valid for as long as @a master is valid.
+ */
+struct GNUNET_PSYC_Channel *
+GNUNET_PSYC_master_get_channel (struct GNUNET_PSYC_Master *master)
+{
+ return &master->chn;
+}
+
+
+/*** SLAVE ***/
+
+
+static void
+slave_connect (struct GNUNET_PSYC_Slave *slv);
+
+
+static void
+slave_reconnect (void *cls)
+{
+ slave_connect (cls);
+}
+
+
+/**
+ * Slave client disconnected from service.
+ *
+ * Reconnect after backoff period.
+ */
+static void
+slave_disconnected (void *cls,
+ enum GNUNET_MQ_Error error)
+{
+ struct GNUNET_PSYC_Slave *slv = cls;
+ struct GNUNET_PSYC_Channel *chn = &slv->chn;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Slave client disconnected (%d), re-connecting\n",
+ (int) error);
+ if (NULL != chn->tmit)
+ {
+ GNUNET_PSYC_transmit_destroy (chn->tmit);
+ chn->tmit = NULL;
+ }
+ if (NULL != chn->mq)
+ {
+ GNUNET_MQ_destroy (chn->mq);
+ chn->mq = NULL;
+ }
+ chn->reconnect_task = GNUNET_SCHEDULER_add_delayed (chn->reconnect_delay,
+ &slave_reconnect,
+ slv);
+ chn->reconnect_delay = GNUNET_TIME_STD_BACKOFF (chn->reconnect_delay);
+}
+
+
+static void
+slave_connect (struct GNUNET_PSYC_Slave *slv)
+{
+ struct GNUNET_PSYC_Channel *chn = &slv->chn;
+
+ struct GNUNET_MQ_MessageHandler handlers[] = {
+ GNUNET_MQ_hd_fixed_size (slave_join_ack,
+ GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN_ACK,
+ struct GNUNET_PSYC_CountersResultMessage,
+ slv),
+ GNUNET_MQ_hd_var_size (slave_join_decision,
+ GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION,
+ struct GNUNET_PSYC_JoinDecisionMessage,
+ slv),
+ GNUNET_MQ_hd_fixed_size (channel_part_ack,
+ GNUNET_MESSAGE_TYPE_PSYC_PART_ACK,
+ struct GNUNET_MessageHeader,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_message,
+ GNUNET_MESSAGE_TYPE_PSYC_MESSAGE,
+ struct GNUNET_PSYC_MessageHeader,
+ chn),
+ GNUNET_MQ_hd_fixed_size (channel_message_ack,
+ GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK,
+ struct GNUNET_MessageHeader,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_history_result,
+ GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT,
+ struct GNUNET_OperationResultMessage,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_state_result,
+ GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT,
+ struct GNUNET_OperationResultMessage,
+ chn),
+ GNUNET_MQ_hd_var_size (channel_result,
+ GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE,
+ struct GNUNET_OperationResultMessage,
+ chn),
+ GNUNET_MQ_handler_end ()
+ };
+
+ chn->mq = GNUNET_CLIENT_connect (chn->cfg,
+ "psyc",
+ handlers,
+ &slave_disconnected,
+ slv);
+ if (NULL == chn->mq)
+ {
+ chn->reconnect_task = GNUNET_SCHEDULER_add_delayed (chn->reconnect_delay,
+ &slave_reconnect,
+ slv);
+ chn->reconnect_delay = GNUNET_TIME_STD_BACKOFF (chn->reconnect_delay);
+ return;
+ }
+ chn->tmit = GNUNET_PSYC_transmit_create (chn->mq);
+
+ GNUNET_MQ_send_copy (chn->mq, chn->connect_env);
+}
+
+
+/**
+ * Join a PSYC channel.
+ *
+ * The entity joining is always the local peer. The user must immediately use
+ * the GNUNET_PSYC_slave_transmit() functions to transmit a @e join_msg to the
+ * channel; if the join request succeeds, the channel state (and @e recent
+ * method calls) will be replayed to the joining member. There is no explicit
+ * notification on failure (as the channel may simply take days to approve,
+ * and disapproval is simply being ignored).
+ *
+ * @param cfg
+ * Configuration to use.
+ * @param channel_key ECC public key that identifies the channel we wish to join.
+ * @param slave_key ECC private-public key pair that identifies the slave, and
+ * used by multicast to sign the join request and subsequent unicast
+ * requests sent to the master.
+ * @param origin Peer identity of the origin.
+ * @param relay_count Number of peers in the @a relays array.
+ * @param relays Peer identities of members of the multicast group, which serve
+ * as relays and used to join the group at.
+ * @param message_cb Function to invoke on message parts received from the
+ * channel, typically at least contains method handlers for @e join and
+ * @e part.
+ * @param slave_connect_cb Function invoked once we have connected to the
+ * PSYC service.
+ * @param join_decision_cb Function invoked once we have received a join
+ * decision.
+ * @param cls Closure for @a message_cb and @a slave_joined_cb.
+ * @param method_name Method name for the join request.
+ * @param env Environment containing transient variables for the request, or NULL.
+ * @param data Payload for the join message.
+ * @param data_size Number of bytes in @a data.
+ *
+ * @return Handle for the slave, NULL on error.
+ */
+struct GNUNET_PSYC_Slave *
+GNUNET_PSYC_slave_join (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *channel_pub_key,
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key,
+ enum GNUNET_PSYC_SlaveJoinFlags flags,
+ const struct GNUNET_PeerIdentity *origin,
+ uint32_t relay_count,
+ const struct GNUNET_PeerIdentity *relays,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_PSYC_SlaveConnectCallback connect_cb,
+ GNUNET_PSYC_JoinDecisionCallback join_decision_cb,
+ void *cls,
+ const struct GNUNET_PSYC_Message *join_msg)
+{
+ struct GNUNET_PSYC_Slave *slv = GNUNET_malloc (sizeof (*slv));
+ struct GNUNET_PSYC_Channel *chn = &slv->chn;
+ uint16_t relay_size = relay_count * sizeof (*relays);
+ uint16_t join_msg_size;
+ if (NULL == join_msg)
+ join_msg_size = 0;
+ else
+ join_msg_size = ntohs (join_msg->header.size);
+
+ struct SlaveJoinRequest *req;
+ chn->connect_env = GNUNET_MQ_msg_extra (req, relay_size + join_msg_size,
+ GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN);
+ req->channel_pub_key = *channel_pub_key;
+ req->slave_key = *slave_key;
+ req->origin = *origin;
+ req->relay_count = htonl (relay_count);
+ req->flags = htonl (flags);
+
+ if (0 < relay_size)
+ GNUNET_memcpy (&req[1], relays, relay_size);
+
+ if (NULL != join_msg)
+ GNUNET_memcpy ((char *) &req[1] + relay_size, join_msg, join_msg_size);
+
+ chn->cfg = cfg;
+ chn->is_master = GNUNET_NO;
+ chn->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS;
+
+ chn->op = GNUNET_OP_create ();
+ chn->recv = GNUNET_PSYC_receive_create (message_cb, message_part_cb, cls);
+
+ slv->connect_cb = connect_cb;
+ slv->join_dcsn_cb = join_decision_cb;
+ slv->cb_cls = cls;
+
+ slave_connect (slv);
+ return slv;
+}
+
+
+/**
+ * Part a PSYC channel.
+ *
+ * Will terminate the connection to the PSYC service. Polite clients should
+ * first explicitly send a part request (via GNUNET_PSYC_slave_transmit()).
+ *
+ * @param slave Slave handle.
+ */
+void
+GNUNET_PSYC_slave_part (struct GNUNET_PSYC_Slave *slv,
+ int keep_active,
+ GNUNET_ContinuationCallback part_cb,
+ void *part_cls)
+{
+ struct GNUNET_PSYC_Channel *chn = &slv->chn;
+ struct GNUNET_MQ_Envelope *env;
+
+ chn->is_disconnecting = GNUNET_YES;
+ chn->disconnect_cb = part_cb;
+ chn->disconnect_cls = part_cls;
+ env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_PART_REQUEST);
+ GNUNET_MQ_send (chn->mq, env);
+}
+
+
+/**
+ * Request a message to be sent to the channel master.
+ *
+ * @param slave Slave handle.
+ * @param method_name Which (PSYC) method should be invoked (on host).
+ * @param notify_mod Function to call to obtain modifiers.
+ * @param notify_data Function to call to obtain fragments of the data.
+ * @param notify_cls Closure for @a notify.
+ * @param flags Flags for the message being transmitted.
+ *
+ * @return Transmission handle, NULL on error (i.e. more than one request
+ * queued).
+ */
+struct GNUNET_PSYC_SlaveTransmitHandle *
+GNUNET_PSYC_slave_transmit (struct GNUNET_PSYC_Slave *slv,
+ const char *method_name,
+ GNUNET_PSYC_TransmitNotifyModifier notify_mod,
+ GNUNET_PSYC_TransmitNotifyData notify_data,
+ void *notify_cls,
+ enum GNUNET_PSYC_SlaveTransmitFlags flags)
+
+{
+ if (GNUNET_OK
+ == GNUNET_PSYC_transmit_message (slv->chn.tmit, method_name, NULL,
+ notify_mod, notify_data, notify_cls,
+ flags))
+ return (struct GNUNET_PSYC_SlaveTransmitHandle *) slv->chn.tmit;
+ else
+ return NULL;
+}
+
+
+/**
+ * Resume transmission to the master.
+ *
+ * @param tmit Handle of the request that is being resumed.
+ */
+void
+GNUNET_PSYC_slave_transmit_resume (struct GNUNET_PSYC_SlaveTransmitHandle *tmit)
+{
+ GNUNET_PSYC_transmit_resume ((struct GNUNET_PSYC_TransmitHandle *) tmit);
+}
+
+
+/**
+ * Abort transmission request to master.
+ *
+ * @param tmit Handle of the request that is being aborted.
+ */
+void
+GNUNET_PSYC_slave_transmit_cancel (struct GNUNET_PSYC_SlaveTransmitHandle *tmit)
+{
+ GNUNET_PSYC_transmit_cancel ((struct GNUNET_PSYC_TransmitHandle *) tmit);
+}
+
+
+/**
+ * Convert @a slave to a @e channel handle to access the @e channel APIs.
+ *
+ * @param slv Slave handle.
+ *
+ * @return Channel handle, valid for as long as @a slave is valid.
+ */
+struct GNUNET_PSYC_Channel *
+GNUNET_PSYC_slave_get_channel (struct GNUNET_PSYC_Slave *slv)
+{
+ return &slv->chn;
+}
+
+
+/**
+ * Add a slave to the channel's membership list.
+ *
+ * Note that this will NOT generate any PSYC traffic, it will merely update the
+ * local database to modify how we react to <em>membership test</em> queries.
+ * The channel master still needs to explicitly transmit a @e join message to
+ * notify other channel members and they then also must still call this function
+ * in their respective methods handling the @e join message. This way, how @e
+ * join and @e part operations are exactly implemented is still up to the
+ * application; for example, there might be a @e part_all method to kick out
+ * everyone.
+ *
+ * Note that channel slaves are explicitly trusted to execute such methods
+ * correctly; not doing so correctly will result in either denying other slaves
+ * access or offering access to channel data to non-members.
+ *
+ * @param chn
+ * Channel handle.
+ * @param slave_pub_key
+ * Identity of channel slave to add.
+ * @param announced_at
+ * ID of the message that announced the membership change.
+ * @param effective_since
+ * Addition of slave is in effect since this message ID.
+ * @param result_cb
+ * Function to call with the result of the operation.
+ * The @e result_code argument is #GNUNET_OK on success, or
+ * #GNUNET_SYSERR on error. In case of an error, the @e data argument
+ * can contain an optional error message.
+ * @param cls
+ * Closure for @a result_cb.
+ */
+void
+GNUNET_PSYC_channel_slave_add (struct GNUNET_PSYC_Channel *chn,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ uint64_t announced_at,
+ uint64_t effective_since,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ struct ChannelMembershipStoreRequest *req;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE);
+ req->slave_pub_key = *slave_pub_key;
+ req->announced_at = GNUNET_htonll (announced_at);
+ req->effective_since = GNUNET_htonll (effective_since);
+ req->did_join = GNUNET_YES;
+ req->op_id = GNUNET_htonll (GNUNET_OP_add (chn->op, result_cb, cls, NULL));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "GNUNET_PSYC_channel_slave_add, OP ID: %" PRIu64 "\n",
+ GNUNET_ntohll (req->op_id));
+ GNUNET_MQ_send (chn->mq, env);
+}
+
+
+/**
+ * Remove a slave from the channel's membership list.
+ *
+ * Note that this will NOT generate any PSYC traffic, it will merely update the
+ * local database to modify how we react to <em>membership test</em> queries.
+ * The channel master still needs to explicitly transmit a @e part message to
+ * notify other channel members and they then also must still call this function
+ * in their respective methods handling the @e part message. This way, how
+ * @e join and @e part operations are exactly implemented is still up to the
+ * application; for example, there might be a @e part_all message to kick out
+ * everyone.
+ *
+ * Note that channel members are explicitly trusted to perform these
+ * operations correctly; not doing so correctly will result in either
+ * denying members access or offering access to channel data to
+ * non-members.
+ *
+ * @param chn
+ * Channel handle.
+ * @param slave_pub_key
+ * Identity of channel slave to remove.
+ * @param announced_at
+ * ID of the message that announced the membership change.
+ * @param result_cb
+ * Function to call with the result of the operation.
+ * The @e result_code argument is #GNUNET_OK on success, or
+ * #GNUNET_SYSERR on error. In case of an error, the @e data argument
+ * can contain an optional error message.
+ * @param cls
+ * Closure for @a result_cb.
+ */
+void
+GNUNET_PSYC_channel_slave_remove (struct GNUNET_PSYC_Channel *chn,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key,
+ uint64_t announced_at,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ struct ChannelMembershipStoreRequest *req;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE);
+ req->slave_pub_key = *slave_pub_key;
+ req->announced_at = GNUNET_htonll (announced_at);
+ req->did_join = GNUNET_NO;
+ req->op_id = GNUNET_htonll (GNUNET_OP_add (chn->op, result_cb, cls, NULL));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "GNUNET_PSYC_channel_slave_remove, OP ID: %" PRIu64 "\n",
+ GNUNET_ntohll (req->op_id));
+ GNUNET_MQ_send (chn->mq, env);
+}
+
+
+static struct GNUNET_PSYC_HistoryRequest *
+channel_history_replay (struct GNUNET_PSYC_Channel *chn,
+ uint64_t start_message_id,
+ uint64_t end_message_id,
+ uint64_t message_limit,
+ const char *method_prefix,
+ uint32_t flags,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ struct GNUNET_PSYC_HistoryRequestMessage *req;
+ struct GNUNET_PSYC_HistoryRequest *hist = GNUNET_malloc (sizeof (*hist));
+ hist->chn = chn;
+ hist->recv = GNUNET_PSYC_receive_create (message_cb, message_part_cb, cls);
+ hist->result_cb = result_cb;
+ hist->cls = cls;
+ hist->op_id = GNUNET_OP_add (chn->op, op_recv_history_result, hist, NULL);
+
+ GNUNET_assert (NULL != method_prefix);
+ uint16_t method_size = strnlen (method_prefix,
+ GNUNET_MAX_MESSAGE_SIZE
+ - sizeof (*req)) + 1;
+ GNUNET_assert ('\0' == method_prefix[method_size - 1]);
+
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (req, method_size,
+ GNUNET_MESSAGE_TYPE_PSYC_HISTORY_REPLAY);
+ req->start_message_id = GNUNET_htonll (start_message_id);
+ req->end_message_id = GNUNET_htonll (end_message_id);
+ req->message_limit = GNUNET_htonll (message_limit);
+ req->flags = htonl (flags);
+ req->op_id = GNUNET_htonll (hist->op_id);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "channel_history_replay, OP ID: %" PRIu64 "\n",
+ GNUNET_ntohll (req->op_id));
+ GNUNET_memcpy (&req[1], method_prefix, method_size);
+
+ GNUNET_MQ_send (chn->mq, env);
+ return hist;
+}
+
+
+/**
+ * Request to replay a part of the message history of the channel.
+ *
+ * Historic messages (but NOT the state at the time) will be replayed and given
+ * to the normal method handlers with a #GNUNET_PSYC_MESSAGE_HISTORIC flag set.
+ *
+ * Messages are retrieved from the local PSYCstore if available,
+ * otherwise requested from the network.
+ *
+ * @param channel
+ * Which channel should be replayed?
+ * @param start_message_id
+ * Earliest interesting point in history.
+ * @param end_message_id
+ * Last (inclusive) interesting point in history.
+ * @param method_prefix
+ * Retrieve only messages with a matching method prefix.
+ * @param flags
+ * OR'ed enum GNUNET_PSYC_HistoryReplayFlags
+ * @param result_cb
+ * Function to call when the requested history has been fully replayed.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle to cancel history replay operation.
+ */
+struct GNUNET_PSYC_HistoryRequest *
+GNUNET_PSYC_channel_history_replay (struct GNUNET_PSYC_Channel *chn,
+ uint64_t start_message_id,
+ uint64_t end_message_id,
+ const char *method_prefix,
+ uint32_t flags,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ return channel_history_replay (chn, start_message_id, end_message_id, 0,
+ method_prefix, flags,
+ message_cb, message_part_cb, result_cb, cls);
+}
+
+
+/**
+ * Request to replay the latest messages from the message history of the channel.
+ *
+ * Historic messages (but NOT the state at the time) will be replayed (given to
+ * the normal method handlers) if available and if access is permitted.
+ *
+ * @param channel
+ * Which channel should be replayed?
+ * @param message_limit
+ * Maximum number of messages to replay.
+ * @param method_prefix
+ * Retrieve only messages with a matching method prefix.
+ * Use NULL or "" to retrieve all.
+ * @param flags
+ * OR'ed enum GNUNET_PSYC_HistoryReplayFlags
+ * @param result_cb
+ * Function to call when the requested history has been fully replayed.
+ * @param cls
+ * Closure for the callbacks.
+ *
+ * @return Handle to cancel history replay operation.
+ */
+struct GNUNET_PSYC_HistoryRequest *
+GNUNET_PSYC_channel_history_replay_latest (struct GNUNET_PSYC_Channel *chn,
+ uint64_t message_limit,
+ const char *method_prefix,
+ uint32_t flags,
+ GNUNET_PSYC_MessageCallback message_cb,
+ GNUNET_PSYC_MessagePartCallback message_part_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ return channel_history_replay (chn, 0, 0, message_limit, method_prefix, flags,
+ message_cb, message_part_cb, result_cb, cls);
+}
+
+
+void
+GNUNET_PSYC_channel_history_replay_cancel (struct GNUNET_PSYC_Channel *channel,
+ struct GNUNET_PSYC_HistoryRequest *hist)
+{
+ GNUNET_PSYC_receive_destroy (hist->recv);
+ GNUNET_OP_remove (hist->chn->op, hist->op_id);
+ GNUNET_free (hist);
+}
+
+
+/**
+ * Retrieve the best matching channel state variable.
+ *
+ * If the requested variable name is not present in the state, the nearest
+ * less-specific name is matched; for example, requesting "_a_b" will match "_a"
+ * if "_a_b" does not exist.
+ *
+ * @param channel
+ * Channel handle.
+ * @param full_name
+ * Full name of the requested variable.
+ * The actual variable returned might have a shorter name.
+ * @param var_cb
+ * Function called once when a matching state variable is found.
+ * Not called if there's no matching state variable.
+ * @param result_cb
+ * Function called after the operation finished.
+ * (i.e. all state variables have been returned via @a state_cb)
+ * @param cls
+ * Closure for the callbacks.
+ */
+static struct GNUNET_PSYC_StateRequest *
+channel_state_get (struct GNUNET_PSYC_Channel *chn,
+ uint16_t type, const char *name,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb, void *cls)
+{
+ struct StateRequest *req;
+ struct GNUNET_PSYC_StateRequest *sr = GNUNET_malloc (sizeof (*sr));
+ sr->chn = chn;
+ sr->var_cb = var_cb;
+ sr->result_cb = result_cb;
+ sr->cls = cls;
+ sr->op_id = GNUNET_OP_add (chn->op, op_recv_state_result, sr, NULL);
+
+ GNUNET_assert (NULL != name);
+ size_t name_size = strnlen (name, GNUNET_MAX_MESSAGE_SIZE
+ - sizeof (*req)) + 1;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (req, name_size, type);
+ req->op_id = GNUNET_htonll (sr->op_id);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "channel_state_get, OP ID: %" PRIu64 "\n",
+ GNUNET_ntohll (req->op_id));
+
+ GNUNET_memcpy (&req[1], name, name_size);
+
+ GNUNET_MQ_send (chn->mq, env);
+ return sr;
+}
+
+
+/**
+ * Retrieve the best matching channel state variable.
+ *
+ * If the requested variable name is not present in the state, the nearest
+ * less-specific name is matched; for example, requesting "_a_b" will match "_a"
+ * if "_a_b" does not exist.
+ *
+ * @param channel
+ * Channel handle.
+ * @param full_name
+ * Full name of the requested variable.
+ * The actual variable returned might have a shorter name.
+ * @param var_cb
+ * Function called once when a matching state variable is found.
+ * Not called if there's no matching state variable.
+ * @param result_cb
+ * Function called after the operation finished.
+ * (i.e. all state variables have been returned via @a state_cb)
+ * @param cls
+ * Closure for the callbacks.
+ */
+struct GNUNET_PSYC_StateRequest *
+GNUNET_PSYC_channel_state_get (struct GNUNET_PSYC_Channel *chn,
+ const char *full_name,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ return channel_state_get (chn, GNUNET_MESSAGE_TYPE_PSYC_STATE_GET,
+ full_name, var_cb, result_cb, cls);
+
+}
+
+
+/**
+ * Return all channel state variables whose name matches a given prefix.
+ *
+ * A name matches if it starts with the given @a name_prefix, thus requesting
+ * the empty prefix ("") will match all values; requesting "_a_b" will also
+ * return values stored under "_a_b_c".
+ *
+ * The @a state_cb is invoked on all matching state variables asynchronously, as
+ * the state is stored in and retrieved from the PSYCstore,
+ *
+ * @param channel
+ * Channel handle.
+ * @param name_prefix
+ * Prefix of the state variable name to match.
+ * @param var_cb
+ * Function called once when a matching state variable is found.
+ * Not called if there's no matching state variable.
+ * @param result_cb
+ * Function called after the operation finished.
+ * (i.e. all state variables have been returned via @a state_cb)
+ * @param cls
+ * Closure for the callbacks.
+ */
+struct GNUNET_PSYC_StateRequest *
+GNUNET_PSYC_channel_state_get_prefix (struct GNUNET_PSYC_Channel *chn,
+ const char *name_prefix,
+ GNUNET_PSYC_StateVarCallback var_cb,
+ GNUNET_ResultCallback result_cb,
+ void *cls)
+{
+ return channel_state_get (chn, GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX,
+ name_prefix, var_cb, result_cb, cls);
+}
+
+
+/**
+ * Cancel a state request operation.
+ *
+ * @param sr
+ * Handle for the operation to cancel.
+ */
+void
+GNUNET_PSYC_channel_state_get_cancel (struct GNUNET_PSYC_StateRequest *sr)
+{
+ GNUNET_OP_remove (sr->chn->op, sr->op_id);
+ GNUNET_free (sr);
+}
+
+/* end of psyc_api.c */
diff --git a/src/psyc/psyc_test_lib.h b/src/psyc/psyc_test_lib.h
new file mode 100644
index 0000000..0ad9910
--- /dev/null
+++ b/src/psyc/psyc_test_lib.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/test_psyc_api_join.c
+ * @brief library for writing psyc tests
+ * @author xrs
+ */
+
+#define MAX_TESTBED_OPS 32
+
+struct pctx
+{
+ int idx;
+
+ struct GNUNET_TESTBED_Peer *testbed_peer;
+
+ const struct GNUNET_PeerIdentity *peer_id;
+
+ const struct GNUNET_PeerIdentity *peer_id_master;
+
+ /**
+ * Used to simulate egos (not peerid)
+ */
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *id_key;
+
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *id_pub_key;
+
+ /**
+ * Used to store either GNUNET_PSYC_Master or GNUNET_PSYC_Slave handle
+ */
+ void *psyc;
+
+ struct GNUNET_PSYC_Channel *channel;
+
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key;
+
+ struct GNUNET_CRYPTO_EddsaPublicKey *channel_pub_key;
+
+ int test_ok;
+};
+
+static struct GNUNET_SCHEDULER_Task *timeout_task_id;
+
+static int result = GNUNET_SYSERR;
+
+static struct GNUNET_TESTBED_Operation *op[MAX_TESTBED_OPS];
+
+static int op_cnt = 0;
+
diff --git a/src/psyc/test_psyc.c b/src/psyc/test_psyc.c
new file mode 100644
index 0000000..b6e27bb
--- /dev/null
+++ b/src/psyc/test_psyc.c
@@ -0,0 +1,1018 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/test_psyc.c
+ * @brief Tests for the PSYC API.
+ * @author Gabor X Toth
+ * @author Christian Grothoff
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_common.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_testing_lib.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_psyc_service.h"
+
+#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * Return value from 'main'.
+ */
+static int res;
+
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+static struct GNUNET_PeerIdentity this_peer;
+
+/**
+ * Handle for task for timeout termination.
+ */
+static struct GNUNET_SCHEDULER_Task * end_badly_task;
+
+static struct GNUNET_PSYC_Master *mst;
+static struct GNUNET_PSYC_Slave *slv;
+
+static struct GNUNET_PSYC_Channel *mst_chn, *slv_chn;
+
+static struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key;
+static struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key;
+
+static struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key;
+static struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+
+struct TransmitClosure
+{
+ struct GNUNET_PSYC_MasterTransmitHandle *mst_tmit;
+ struct GNUNET_PSYC_SlaveTransmitHandle *slv_tmit;
+ struct GNUNET_PSYC_Environment *env;
+ struct GNUNET_PSYC_Modifier *mod;
+ char *data[16];
+ const char *mod_value;
+ size_t mod_value_size;
+ uint8_t data_delay[16];
+ uint8_t data_count;
+ uint8_t paused;
+ uint8_t n;
+};
+
+static struct TransmitClosure *tmit;
+
+static uint8_t join_req_count, end_count;
+
+enum
+{
+ TEST_NONE = 0,
+ TEST_MASTER_START = 1,
+ TEST_SLAVE_JOIN_REJECT = 2,
+ TEST_SLAVE_JOIN_ACCEPT = 3,
+ TEST_SLAVE_ADD = 4,
+ TEST_SLAVE_REMOVE = 5,
+ TEST_SLAVE_TRANSMIT = 6,
+ TEST_MASTER_TRANSMIT = 7,
+ TEST_MASTER_HISTORY_REPLAY_LATEST = 8,
+ TEST_SLAVE_HISTORY_REPLAY_LATEST = 9,
+ TEST_MASTER_HISTORY_REPLAY = 10,
+ TEST_SLAVE_HISTORY_REPLAY = 11,
+ TEST_MASTER_STATE_GET = 12,
+ TEST_SLAVE_STATE_GET = 13,
+ TEST_MASTER_STATE_GET_PREFIX = 14,
+ TEST_SLAVE_STATE_GET_PREFIX = 15,
+} test;
+
+
+static void
+master_transmit ();
+
+static void
+master_history_replay_latest ();
+
+
+static void
+master_stopped (void *cls)
+{
+ if (NULL != tmit)
+ {
+ GNUNET_PSYC_env_destroy (tmit->env);
+ GNUNET_free (tmit);
+ tmit = NULL;
+ }
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+slave_parted (void *cls)
+{
+ if (NULL != mst)
+ {
+ GNUNET_PSYC_master_stop (mst, GNUNET_NO, &master_stopped, NULL);
+ mst = NULL;
+ }
+ else
+ master_stopped (NULL);
+}
+
+
+/**
+ * Clean up all resources used.
+ */
+static void
+cleanup ()
+{
+ if (NULL != slv)
+ {
+ GNUNET_PSYC_slave_part (slv, GNUNET_NO, &slave_parted, NULL);
+ slv = NULL;
+ }
+ else
+ slave_parted (NULL);
+}
+
+
+/**
+ * Terminate the test case (failure).
+ *
+ * @param cls NULL
+ */
+static void
+end_badly (void *cls)
+{
+ res = 1;
+ cleanup ();
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Test FAILED.\n");
+}
+
+
+/**
+ * Terminate the test case (success).
+ *
+ * @param cls NULL
+ */
+static void
+end_normally (void *cls)
+{
+ res = 0;
+ cleanup ();
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Test PASSED.\n");
+}
+
+
+/**
+ * Finish the test case (successfully).
+ */
+static void
+end ()
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Ending tests.\n");
+
+ if (end_badly_task != NULL)
+ {
+ GNUNET_SCHEDULER_cancel (end_badly_task);
+ end_badly_task = NULL;
+ }
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS,
+ &end_normally, NULL);
+}
+
+
+static void
+master_message_cb (void *cls, const struct GNUNET_PSYC_MessageHeader *msg)
+{
+ GNUNET_assert (NULL != msg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Master got PSYC message fragment of size %u "
+ "belonging to message ID %" PRIu64 " with flags %x\n",
+ test, ntohs (msg->header.size),
+ GNUNET_ntohll (msg->message_id), ntohl (msg->flags));
+ // FIXME
+}
+
+
+static void
+master_message_part_cb (void *cls, const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg)
+{
+ GNUNET_assert (NULL != msg && NULL != pmsg);
+
+ uint64_t message_id = GNUNET_ntohll (msg->message_id);
+ uint32_t flags = ntohl (msg->flags);
+
+ uint16_t type = ntohs (pmsg->type);
+ uint16_t size = ntohs (pmsg->size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Master got message part of type %u and size %u "
+ "belonging to message ID %" PRIu64 " with flags %x\n",
+ test, type, size, message_id, flags);
+
+ switch (test)
+ {
+ case TEST_SLAVE_TRANSMIT:
+ if (GNUNET_PSYC_MESSAGE_REQUEST != flags)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Test #%d: Unexpected request flags: %x" PRIu32 "\n",
+ test, flags);
+ GNUNET_assert (0);
+ return;
+ }
+ // FIXME: check rest of message
+
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END == type)
+ master_transmit ();
+ break;
+
+ case TEST_MASTER_TRANSMIT:
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END == type && 2 == ++end_count)
+ master_history_replay_latest ();
+ break;
+
+ case TEST_MASTER_HISTORY_REPLAY:
+ case TEST_MASTER_HISTORY_REPLAY_LATEST:
+ if (GNUNET_PSYC_MESSAGE_HISTORIC != flags)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Test #%d: Unexpected flags for historic message: %x" PRIu32 "\n",
+ test, flags);
+ GNUNET_assert (0);
+ return;
+ }
+ break;
+
+ default:
+ GNUNET_assert (0);
+ }
+}
+
+
+static void
+slave_message_cb (void *cls, const struct GNUNET_PSYC_MessageHeader *msg)
+{
+ GNUNET_assert (NULL != msg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Slave got PSYC message fragment of size %u "
+ "belonging to message ID %" PRIu64 " with flags %x\n",
+ test, ntohs (msg->header.size),
+ GNUNET_ntohll (msg->message_id), ntohl (msg->flags));
+ // FIXME
+}
+
+
+static void
+slave_message_part_cb (void *cls,
+ const struct GNUNET_PSYC_MessageHeader *msg,
+ const struct GNUNET_MessageHeader *pmsg)
+{
+ GNUNET_assert (NULL != msg && NULL != pmsg);
+
+ uint64_t message_id = GNUNET_ntohll (msg->message_id);
+ uint32_t flags = ntohl (msg->flags);
+
+ uint16_t type = ntohs (pmsg->type);
+ uint16_t size = ntohs (pmsg->size);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Slave got message part of type %u and size %u "
+ "belonging to message ID %" PRIu64 " with flags %x\n",
+ test, type, size, message_id, flags);
+
+ switch (test)
+ {
+ case TEST_MASTER_TRANSMIT:
+ if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END == type && 2 == ++end_count)
+ master_history_replay_latest ();
+ break;
+
+ case TEST_SLAVE_HISTORY_REPLAY:
+ case TEST_SLAVE_HISTORY_REPLAY_LATEST:
+ if (GNUNET_PSYC_MESSAGE_HISTORIC != flags)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Test #%d: Unexpected flags for historic message: %x" PRIu32 "\n",
+ test, flags);
+ GNUNET_assert (0);
+ return;
+ }
+ break;
+
+ default:
+ GNUNET_assert (0);
+ }
+}
+
+
+static void
+state_get_var (void *cls, const struct GNUNET_MessageHeader *mod,
+ const char *name, const void *value,
+ uint32_t value_size, uint32_t full_value_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got state var: %s\n%.*s\n",
+ name,
+ (int) value_size,
+ (const char *) value);
+}
+
+
+/*** Slave state_get_prefix() ***/
+
+static void
+slave_state_get_prefix_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: slave_state_get_prefix:\t%" PRId64 " (%.*s)\n",
+ test, result,
+ (int) err_msg_size,
+ (const char *) err_msg);
+ // FIXME: GNUNET_assert (2 == result);
+ end ();
+}
+
+
+static void
+slave_state_get_prefix ()
+{
+ test = TEST_SLAVE_STATE_GET_PREFIX;
+ GNUNET_PSYC_channel_state_get_prefix (slv_chn, "_foo", state_get_var,
+ slave_state_get_prefix_result, NULL);
+}
+
+
+/*** Master state_get_prefix() ***/
+
+
+static void
+master_state_get_prefix_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: master_state_get_prefix:\t%" PRId64 " (%s)\n",
+ test, result, (char *) err_msg);
+ // FIXME: GNUNET_assert (2 == result);
+ slave_state_get_prefix ();
+}
+
+
+static void
+master_state_get_prefix ()
+{
+ test = TEST_MASTER_STATE_GET_PREFIX;
+ GNUNET_PSYC_channel_state_get_prefix (mst_chn, "_foo", state_get_var,
+ master_state_get_prefix_result, NULL);
+}
+
+
+/*** Slave state_get() ***/
+
+
+static void
+slave_state_get_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: slave_state_get:\t%" PRId64 " (%.*s)\n",
+ test, result, err_msg_size, (char *) err_msg);
+ // FIXME: GNUNET_assert (2 == result);
+ master_state_get_prefix ();
+}
+
+
+static void
+slave_state_get ()
+{
+ test = TEST_SLAVE_STATE_GET;
+ GNUNET_PSYC_channel_state_get (slv_chn, "_foo_bar_baz", state_get_var,
+ slave_state_get_result, NULL);
+}
+
+
+/*** Master state_get() ***/
+
+
+static void
+master_state_get_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: master_state_get:\t%" PRId64 " (%.*s)\n",
+ test, result, err_msg_size, (char *) err_msg);
+ // FIXME: GNUNET_assert (1 == result);
+ slave_state_get ();
+}
+
+
+static void
+master_state_get ()
+{
+ test = TEST_MASTER_STATE_GET;
+ GNUNET_PSYC_channel_state_get (mst_chn, "_foo_bar_baz", state_get_var,
+ master_state_get_result, NULL);
+}
+
+
+/*** Slave history_replay() ***/
+
+static void
+slave_history_replay_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: slave_history_replay:\t%" PRId64 " (%.*s)\n",
+ test, result,
+ (int) err_msg_size,
+ (const char *) err_msg);
+ GNUNET_assert (9 == result);
+
+ master_state_get ();
+}
+
+
+static void
+slave_history_replay ()
+{
+ test = TEST_SLAVE_HISTORY_REPLAY;
+ GNUNET_PSYC_channel_history_replay (slv_chn, 1, 1, "",
+ GNUNET_PSYC_HISTORY_REPLAY_LOCAL,
+ slave_message_cb,
+ slave_message_part_cb,
+ slave_history_replay_result, NULL);
+}
+
+
+/*** Master history_replay() ***/
+
+
+static void
+master_history_replay_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: master_history_replay:\t%" PRId64 " (%.*s)\n",
+ test, result,
+ (int) err_msg_size,
+ (const char *) err_msg);
+ GNUNET_assert (9 == result);
+
+ slave_history_replay ();
+}
+
+
+static void
+master_history_replay ()
+{
+ test = TEST_MASTER_HISTORY_REPLAY;
+ GNUNET_PSYC_channel_history_replay (mst_chn, 1, 1, "",
+ GNUNET_PSYC_HISTORY_REPLAY_LOCAL,
+ master_message_cb,
+ master_message_part_cb,
+ master_history_replay_result, NULL);
+}
+
+
+/*** Slave history_replay_latest() ***/
+
+
+static void
+slave_history_replay_latest_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: slave_history_replay_latest:\t%" PRId64 " (%.*s)\n",
+ test, result,
+ (int) err_msg_size,
+ (const char *) err_msg);
+ GNUNET_assert (9 == result);
+
+ master_history_replay ();
+}
+
+
+static void
+slave_history_replay_latest ()
+{
+ test = TEST_SLAVE_HISTORY_REPLAY_LATEST;
+ GNUNET_PSYC_channel_history_replay_latest (slv_chn, 1, "",
+ GNUNET_PSYC_HISTORY_REPLAY_LOCAL,
+ &slave_message_cb,
+ &slave_message_part_cb,
+ &slave_history_replay_latest_result,
+ NULL);
+}
+
+
+/*** Master history_replay_latest() ***/
+
+
+static void
+master_history_replay_latest_result (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: master_history_replay_latest:\t%" PRId64 " (%.*s)\n",
+ test, result, err_msg_size, (char *) err_msg);
+ GNUNET_assert (9 == result);
+
+ slave_history_replay_latest ();
+}
+
+
+static void
+master_history_replay_latest ()
+{
+ test = TEST_MASTER_HISTORY_REPLAY_LATEST;
+ GNUNET_PSYC_channel_history_replay_latest (mst_chn, 1, "",
+ GNUNET_PSYC_HISTORY_REPLAY_LOCAL,
+ &master_message_cb,
+ &master_message_part_cb,
+ &master_history_replay_latest_result,
+ NULL);
+}
+
+
+static void
+transmit_resume (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%d: Transmission resumed.\n", test);
+ struct TransmitClosure *tmit = cls;
+ if (NULL != tmit->mst_tmit)
+ GNUNET_PSYC_master_transmit_resume (tmit->mst_tmit);
+ else
+ GNUNET_PSYC_slave_transmit_resume (tmit->slv_tmit);
+}
+
+
+static int
+tmit_notify_data (void *cls, uint16_t *data_size, void *data)
+{
+ struct TransmitClosure *tmit = cls;
+ if (0 == tmit->data_count)
+ {
+ *data_size = 0;
+ return GNUNET_YES;
+ }
+
+ uint16_t size = strlen (tmit->data[tmit->n]);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%d: Transmit notify data: %u bytes available, "
+ "processing fragment %u/%u (size %u).\n",
+ test, *data_size, tmit->n + 1, tmit->data_count, size);
+ if (*data_size < size)
+ {
+ *data_size = 0;
+ GNUNET_assert (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_YES != tmit->paused && 0 < tmit->data_delay[tmit->n])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%d: Transmission paused.\n", test);
+ tmit->paused = GNUNET_YES;
+ GNUNET_SCHEDULER_add_delayed (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+ tmit->data_delay[tmit->n]),
+ &transmit_resume, tmit);
+ *data_size = 0;
+ return GNUNET_NO;
+ }
+ tmit->paused = GNUNET_NO;
+
+ *data_size = size;
+ GNUNET_memcpy (data, tmit->data[tmit->n], size);
+
+ return ++tmit->n < tmit->data_count ? GNUNET_NO : GNUNET_YES;
+}
+
+
+static int
+tmit_notify_mod (void *cls, uint16_t *data_size, void *data, uint8_t *oper,
+ uint32_t *full_value_size)
+{
+ struct TransmitClosure *tmit = cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%d: Transmit notify modifier: %u bytes available, "
+ "%u modifiers left to process.\n",
+ test, *data_size, (unsigned int) GNUNET_PSYC_env_get_count (tmit->env));
+
+ uint16_t name_size = 0;
+ size_t value_size = 0;
+ const char *value = NULL;
+
+ if (NULL != oper && NULL != tmit->mod)
+ { /* New modifier */
+ tmit->mod = tmit->mod->next;
+ if (NULL == tmit->mod)
+ { /* No more modifiers, continue with data */
+ *data_size = 0;
+ return GNUNET_YES;
+ }
+
+ GNUNET_assert (tmit->mod->value_size < UINT32_MAX);
+ *full_value_size = tmit->mod->value_size;
+ *oper = tmit->mod->oper;
+ name_size = strlen (tmit->mod->name);
+
+ if (name_size + 1 + tmit->mod->value_size <= *data_size)
+ {
+ *data_size = name_size + 1 + tmit->mod->value_size;
+ }
+ else
+ {
+ tmit->mod_value_size = tmit->mod->value_size;
+ value_size = *data_size - name_size - 1;
+ tmit->mod_value_size -= value_size;
+ tmit->mod_value = tmit->mod->value + value_size;
+ }
+
+ GNUNET_memcpy (data, tmit->mod->name, name_size);
+ ((char *)data)[name_size] = '\0';
+ GNUNET_memcpy ((char *)data + name_size + 1, tmit->mod->value, value_size);
+ }
+ else if (NULL != tmit->mod_value && 0 < tmit->mod_value_size)
+ { /* Modifier continuation */
+ value = tmit->mod_value;
+ if (tmit->mod_value_size <= *data_size)
+ {
+ value_size = tmit->mod_value_size;
+ tmit->mod_value = NULL;
+ }
+ else
+ {
+ value_size = *data_size;
+ tmit->mod_value += value_size;
+ }
+ tmit->mod_value_size -= value_size;
+
+ if (*data_size < value_size)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "value larger than buffer: %u < %zu\n",
+ *data_size, value_size);
+ *data_size = 0;
+ return GNUNET_NO;
+ }
+
+ *data_size = value_size;
+ GNUNET_memcpy (data, value, value_size);
+ }
+
+ return GNUNET_NO;
+}
+
+
+static void
+slave_join ();
+
+
+static void
+slave_transmit ()
+{
+ test = TEST_SLAVE_TRANSMIT;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Slave sending request to master.\n", test);
+
+ tmit = GNUNET_new (struct TransmitClosure);
+ tmit->env = GNUNET_PSYC_env_create ();
+ GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN,
+ "_abc", "abc def", 7);
+ GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN,
+ "_abc_def", "abc def ghi", 11);
+ tmit->mod = GNUNET_PSYC_env_head (tmit->env);
+ tmit->n = 0;
+ tmit->data[0] = "slave test";
+ tmit->data_count = 1;
+ tmit->slv_tmit
+ = GNUNET_PSYC_slave_transmit (slv, "_request_test", &tmit_notify_mod,
+ &tmit_notify_data, tmit,
+ GNUNET_PSYC_SLAVE_TRANSMIT_NONE);
+}
+
+
+static void
+slave_remove_cb (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: slave_remove:\t%" PRId64 " (%.*s)\n",
+ test, result, err_msg_size, (char *) err_msg);
+
+ slave_transmit ();
+}
+
+
+static void
+slave_remove ()
+{
+ test = TEST_SLAVE_REMOVE;
+ struct GNUNET_PSYC_Channel *chn = GNUNET_PSYC_master_get_channel (mst);
+ GNUNET_PSYC_channel_slave_remove (chn, &slave_pub_key, 2,
+ &slave_remove_cb, chn);
+}
+
+
+static void
+slave_add_cb (void *cls, int64_t result,
+ const void *err_msg, uint16_t err_msg_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: slave_add:\t%" PRId64 " (%.*s)\n",
+ test, result, err_msg_size, (char *) err_msg);
+ slave_remove ();
+}
+
+
+static void
+slave_add ()
+{
+ test = TEST_SLAVE_ADD;
+ struct GNUNET_PSYC_Channel *chn = GNUNET_PSYC_master_get_channel (mst);
+ GNUNET_PSYC_channel_slave_add (chn, &slave_pub_key, 2, 2, &slave_add_cb, chn);
+}
+
+
+static void
+schedule_second_slave_join (void *cls)
+{
+ slave_join (TEST_SLAVE_JOIN_ACCEPT);
+}
+
+
+static void
+first_slave_parted (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "First slave parted.\n");
+ GNUNET_SCHEDULER_add_now (&schedule_second_slave_join, NULL);
+}
+
+
+static void
+schedule_first_slave_part (void *cls)
+{
+ GNUNET_PSYC_slave_part (slv, GNUNET_NO, &first_slave_parted, NULL);
+}
+
+
+static void
+join_decision_cb (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn,
+ int is_admitted,
+ const struct GNUNET_PSYC_Message *join_msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Slave got join decision: %d\n", test, is_admitted);
+
+ switch (test)
+ {
+ case TEST_SLAVE_JOIN_REJECT:
+ GNUNET_assert (0 == is_admitted);
+ GNUNET_assert (1 == join_req_count);
+ GNUNET_SCHEDULER_add_now (&schedule_first_slave_part, NULL);
+ break;
+
+ case TEST_SLAVE_JOIN_ACCEPT:
+ GNUNET_assert (1 == is_admitted);
+ GNUNET_assert (2 == join_req_count);
+ slave_add ();
+ break;
+
+ default:
+ GNUNET_break (0);
+ }
+}
+
+
+static void
+join_request_cb (void *cls,
+ const struct GNUNET_PSYC_JoinRequestMessage *req,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ const struct GNUNET_PSYC_Message *join_msg,
+ struct GNUNET_PSYC_JoinHandle *jh)
+{
+ struct GNUNET_HashCode slave_key_hash;
+ GNUNET_CRYPTO_hash (slave_key, sizeof (*slave_key), &slave_key_hash);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Got join request #%u from %s.\n",
+ test, join_req_count, GNUNET_h2s (&slave_key_hash));
+
+ /* Reject first request */
+ int is_admitted = (0 < join_req_count++) ? GNUNET_YES : GNUNET_NO;
+ GNUNET_PSYC_join_decision (jh, is_admitted, 0, NULL, NULL);
+}
+
+
+static void
+slave_connect_cb (void *cls, int result, uint64_t max_message_id)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Slave connected: %d, max_message_id: %" PRIu64 "\n",
+ test, result, max_message_id);
+ GNUNET_assert (TEST_SLAVE_JOIN_REJECT == test || TEST_SLAVE_JOIN_ACCEPT == test);
+ GNUNET_assert (GNUNET_OK == result || GNUNET_NO == result);
+}
+
+
+static void
+slave_join (int t)
+{
+ test = t;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Joining slave.\n", t);
+
+ struct GNUNET_PeerIdentity origin = this_peer;
+ struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
+ GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN,
+ "_foo", "bar baz", 7);
+ GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN,
+ "_foo_bar", "foo bar baz", 11);
+ struct GNUNET_PSYC_Message *
+ join_msg = GNUNET_PSYC_message_create ("_request_join", env, "some data", 9);
+
+ slv = GNUNET_PSYC_slave_join (cfg,
+ &channel_pub_key,
+ slave_key,
+ GNUNET_PSYC_SLAVE_JOIN_NONE,
+ &origin,
+ 0,
+ NULL,
+ &slave_message_cb,
+ &slave_message_part_cb,
+ &slave_connect_cb,
+ &join_decision_cb,
+ NULL,
+ join_msg);
+ GNUNET_free (join_msg);
+ slv_chn = GNUNET_PSYC_slave_get_channel (slv);
+ GNUNET_PSYC_env_destroy (env);
+}
+
+
+static void
+master_transmit ()
+{
+ test = TEST_MASTER_TRANSMIT;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Master sending message to all.\n", test);
+ end_count = 0;
+
+ uint32_t i, j;
+
+ char *name_max = "_test_max";
+ uint8_t name_max_size = sizeof ("_test_max");
+ char *val_max = GNUNET_malloc (GNUNET_PSYC_MODIFIER_MAX_PAYLOAD);
+ for (i = 0; i < GNUNET_PSYC_MODIFIER_MAX_PAYLOAD; i++)
+ val_max[i] = (0 == i % 10000) ? '0' + i / 10000 : '.';
+
+ char *name_cont = "_test_cont";
+ uint8_t name_cont_size = sizeof ("_test_cont");
+ char *val_cont = GNUNET_malloc (GNUNET_PSYC_MODIFIER_MAX_PAYLOAD
+ + GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD);
+ for (i = 0; i < GNUNET_PSYC_MODIFIER_MAX_PAYLOAD - name_cont_size; i++)
+ val_cont[i] = (0 == i % 10000) ? '0' + i / 10000 : ':';
+ for (j = 0; j < GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD; j++, i++)
+ val_cont[i] = (0 == j % 10000) ? '0' + j / 10000 : '!';
+
+ tmit = GNUNET_new (struct TransmitClosure);
+ tmit->env = GNUNET_PSYC_env_create ();
+ GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN,
+ "_foo", "bar baz", 7);
+ GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN,
+ name_max, val_max,
+ GNUNET_PSYC_MODIFIER_MAX_PAYLOAD
+ - name_max_size);
+ GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN,
+ "_foo_bar", "foo bar baz", 11);
+ GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN,
+ name_cont, val_cont,
+ GNUNET_PSYC_MODIFIER_MAX_PAYLOAD - name_cont_size
+ + GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD);
+ tmit->mod = GNUNET_PSYC_env_head (tmit->env);
+ tmit->data[0] = "foo";
+ tmit->data[1] = GNUNET_malloc (GNUNET_PSYC_DATA_MAX_PAYLOAD + 1);
+ for (i = 0; i < GNUNET_PSYC_DATA_MAX_PAYLOAD; i++)
+ tmit->data[1][i] = (0 == i % 10000) ? '0' + i / 10000 : '_';
+ tmit->data[2] = "foo bar";
+ tmit->data[3] = "foo bar baz";
+ tmit->data_delay[1] = 3;
+ tmit->data_count = 4;
+ tmit->mst_tmit
+ = GNUNET_PSYC_master_transmit (mst, "_notice_test", &tmit_notify_mod,
+ &tmit_notify_data, tmit,
+ GNUNET_PSYC_MASTER_TRANSMIT_INC_GROUP_GEN);
+}
+
+
+static void
+master_start_cb (void *cls, int result, uint64_t max_message_id)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Test #%d: Master started: %d, max_message_id: %" PRIu64 "\n",
+ test, result, max_message_id);
+ GNUNET_assert (TEST_MASTER_START == test);
+ GNUNET_assert (GNUNET_OK == result || GNUNET_NO == result);
+ slave_join (TEST_SLAVE_JOIN_REJECT);
+}
+
+
+static void
+master_start ()
+{
+ test = TEST_MASTER_START;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Test #%d: Starting master.\n", test);
+ mst = GNUNET_PSYC_master_start (cfg, channel_key, GNUNET_PSYC_CHANNEL_PRIVATE,
+ &master_start_cb, &join_request_cb,
+ &master_message_cb, &master_message_part_cb,
+ NULL);
+ mst_chn = GNUNET_PSYC_master_get_channel (mst);
+}
+
+
+static void
+schedule_master_start (void *cls)
+{
+ master_start ();
+}
+
+
+/**
+ * Main function of the test, run from scheduler.
+ *
+ * @param cls NULL
+ * @param cfg configuration we use (also to connect to PSYC service)
+ * @param peer handle to access more of the peer (not used)
+ */
+static void
+#if DEBUG_TEST_PSYC
+run (void *cls, char *const *args, const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+#else
+run (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *c,
+ struct GNUNET_TESTING_Peer *peer)
+#endif
+{
+ cfg = c;
+ end_badly_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_badly, NULL);
+
+ GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer);
+
+ channel_key = GNUNET_CRYPTO_eddsa_key_create ();
+ slave_key = GNUNET_CRYPTO_ecdsa_key_create ();
+
+ GNUNET_CRYPTO_eddsa_key_get_public (channel_key, &channel_pub_key);
+ GNUNET_CRYPTO_ecdsa_key_get_public (slave_key, &slave_pub_key);
+
+#if DEBUG_TEST_PSYC
+ master_start ();
+#else
+ /* Allow some time for the services to initialize. */
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
+ &schedule_master_start, NULL);
+#endif
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ res = 1;
+#if DEBUG_TEST_PSYC
+ const struct GNUNET_GETOPT_CommandLineOption opts[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+ if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "test-psyc",
+ "test-psyc [options]",
+ opts, &run, NULL))
+ return 1;
+#else
+ if (0 != GNUNET_TESTING_peer_run ("test-psyc", "test_psyc.conf", &run, NULL))
+ return 1;
+#endif
+ return res;
+}
+
+/* end of test_psyc.c */
diff --git a/src/psyc/test_psyc2.c b/src/psyc/test_psyc2.c
new file mode 100644
index 0000000..c6e7237
--- /dev/null
+++ b/src/psyc/test_psyc2.c
@@ -0,0 +1,284 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/test_psyc2.c
+ * @brief Testbed test for the PSYC API.
+ * @author xrs
+ */
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_common.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_testbed_service.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_psyc_service.h"
+
+#define PEERS_REQUESTED 2
+
+static int result;
+
+static struct GNUNET_SCHEDULER_Task *timeout_tid;
+static struct pctx **pctx;
+
+static struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key;
+static struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key;
+
+static struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key;
+static struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key;
+
+/**
+ * Task To perform tests
+ */
+static struct GNUNET_SCHEDULER_Task *test_task;
+
+/**
+ * Peer id couter
+ */
+static unsigned int pids;
+
+struct pctx
+{
+ int idx;
+ struct GNUNET_TESTBED_Peer *peer;
+ const struct GNUNET_PeerIdentity *id;
+
+ struct GNUNET_TESTBED_Operation *op;
+
+ /**
+ * psyc service handle
+ */
+ void *psyc;
+ struct GNUNET_PSYC_Master *mst;
+ struct GNUNET_PSYC_Slave *slv;
+
+ /**
+ * result for test on peer
+ */
+ int test_ok;
+};
+
+static void
+shutdown_task (void *cls)
+{
+ if (NULL != pctx)
+ {
+ if (NULL != pctx[0]->mst)
+ GNUNET_PSYC_master_stop (pctx[0]->mst, GNUNET_NO, NULL, NULL);
+
+ for (int i=0; i < PEERS_REQUESTED; i++)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Operation done.\n");
+ GNUNET_TESTBED_operation_done (pctx[i]->op);
+ GNUNET_free_non_null (pctx[i]);
+ }
+ GNUNET_free (pctx);
+ }
+
+ if (NULL != timeout_tid)
+ GNUNET_SCHEDULER_cancel (timeout_tid);
+}
+
+static void
+timeout_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Timeout!\n");
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+static void
+start_test (void *cls)
+{
+}
+
+static void
+pinfo_cb (void *cls,
+ struct GNUNET_TESTBED_Operation *operation,
+ const struct GNUNET_TESTBED_PeerInformation *pinfo,
+ const char *emsg)
+{
+ struct pctx *pc = (struct pctx*) cls;
+
+ pc->id = pinfo->result.id;
+
+ pids++;
+ if (pids < (PEERS_REQUESTED - 1))
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got all IDs, starting test\n");
+ test_task = GNUNET_SCHEDULER_add_now (&start_test, NULL);
+}
+
+static void
+mst_start_cb ()
+{
+}
+
+static void
+join_request_cb ()
+{
+}
+
+static void
+mst_message_cb ()
+{
+}
+
+static void
+mst_message_part_cb ()
+{
+}
+
+static void
+slv_message_cb ()
+{
+}
+
+static void
+slv_message_part_cb ()
+{
+}
+
+static void
+slv_connect_cb ()
+{
+}
+
+static void
+join_decision_cb ()
+{
+}
+
+static void *
+psyc_ca (void *cls,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_PSYC_Message *join_msg = NULL;
+ struct pctx *pc = (struct pctx *) cls;
+
+ if (0 == pc->idx)
+ {
+ pc->mst = GNUNET_PSYC_master_start (cfg, channel_key,
+ GNUNET_PSYC_CHANNEL_PRIVATE,
+ &mst_start_cb, &join_request_cb,
+ &mst_message_cb, &mst_message_part_cb,
+ NULL);
+ return pc->mst;
+ }
+
+ pc->slv = GNUNET_PSYC_slave_join (cfg, &channel_pub_key, slave_key,
+ GNUNET_PSYC_SLAVE_JOIN_NONE,
+ &pid, 0, NULL, &slv_message_cb,
+ &slv_message_part_cb,
+ &slv_connect_cb, &join_decision_cb,
+ NULL, join_msg);
+ return pc->slv;
+}
+
+static void
+psyc_da (void *cls,
+ void *op_result)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Disconnected from service.\n");
+}
+
+static void
+service_connect (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ void *ca_result,
+ const char *emsg)
+{
+ struct pctx *pc = (struct pctx *) cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Connected to service\n");
+
+ GNUNET_assert (NULL != ca_result);
+
+ // FIXME: we need a simple service handle to connect to the service, then
+ // get peer information and AFTER that make PSYC ops. Compare to CADET.
+ pc->psyc = ca_result;
+
+ GNUNET_TESTBED_peer_get_information (pc->peer,
+ GNUNET_TESTBED_PIT_IDENTITY,
+ pinfo_cb, pc);
+}
+
+static void
+testbed_master (void *cls,
+ struct GNUNET_TESTBED_RunHandle *h,
+ unsigned int num_peers,
+ struct GNUNET_TESTBED_Peer **p,
+ unsigned int links_succeeded,
+ unsigned int links_failed)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to testbed_master()\n");
+
+ // Create ctx for peers
+ pctx = GNUNET_new_array (PEERS_REQUESTED, struct pctx*);
+ for (int i = 0; i<PEERS_REQUESTED; i++)
+ {
+ pctx[i] = GNUNET_new (struct pctx);
+ pctx[i]->idx = i;
+ pctx[i]->peer = p[i];
+ pctx[i]->id = NULL;
+ pctx[i]->mst = NULL;
+ pctx[i]->op = NULL;
+ pctx[i]->test_ok = GNUNET_NO;
+ }
+
+ channel_key = GNUNET_CRYPTO_eddsa_key_create ();
+ slave_key = GNUNET_CRYPTO_ecdsa_key_create ();
+
+ GNUNET_CRYPTO_eddsa_key_get_public (channel_key, &channel_pub_key);
+ GNUNET_CRYPTO_ecdsa_key_get_public (slave_key, &slave_pub_key);
+
+ pctx[0]->op =
+ GNUNET_TESTBED_service_connect (NULL, p[0], "psyc", service_connect,
+ pctx[0], psyc_ca, psyc_da, pctx[0]);
+
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
+
+ timeout_tid =
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 5),
+ &timeout_task, NULL);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "test\n");
+
+ result = GNUNET_SYSERR;
+
+ ret = GNUNET_TESTBED_test_run ("test-psyc2", "test_psyc.conf",
+ PEERS_REQUESTED, 0LL, NULL, NULL,
+ testbed_master, NULL);
+
+ if ((GNUNET_OK != ret) || (GNUNET_OK != result))
+ return 1;
+
+ return 0;
+}
+
+/* end of test-psyc2.c */
diff --git a/src/psyc/test_psyc_api_join.c b/src/psyc/test_psyc_api_join.c
new file mode 100644
index 0000000..419fa11
--- /dev/null
+++ b/src/psyc/test_psyc_api_join.c
@@ -0,0 +1,282 @@
+/*
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psyc/test_psyc_api_join.c
+ * @brief Testbed test for the PSYC API.
+ * @author xrs
+ */
+
+/**
+ * Lessons Learned:
+ * - define topology in config
+ * - psyc slave join needs part to end (same with master)
+ * - GNUNET_SCHEDULER_add_delayed return value will outdate at call time
+ * - main can not contain GNUNET_log()
+ */
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_common.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_testbed_service.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_psyc_service.h"
+#include "psyc_test_lib.h"
+
+static struct pctx PEERS[2];
+
+static int pids;
+
+
+static void
+shutdown_task (void *cls)
+{
+ if (NULL != timeout_task_id) {
+ GNUNET_SCHEDULER_cancel (timeout_task_id);
+ timeout_task_id = NULL;
+ }
+
+ for (int i=0;i<2;i++) {
+ GNUNET_free (PEERS[i].channel_pub_key);
+
+ if (NULL != PEERS[i].psyc)
+ {
+ if (0 == i)
+ GNUNET_PSYC_master_stop (PEERS[i].psyc, GNUNET_NO, NULL, NULL);
+ else
+ GNUNET_PSYC_slave_part (PEERS[i].psyc, GNUNET_NO, NULL, NULL);
+ }
+ }
+
+ for (int i=0;i<MAX_TESTBED_OPS;i++)
+ if (NULL != op[i])
+ GNUNET_TESTBED_operation_done (op[i]);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Shut down!\n");
+}
+
+static void
+timeout_task (void *cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Timeout!\n");
+
+ timeout_task_id = NULL;
+
+ result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+static void
+join_decision_cb (void *cls,
+ const struct GNUNET_PSYC_JoinDecisionMessage *dcsn,
+ int is_admitted,
+ const struct GNUNET_PSYC_Message *join_msg)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "slave: got join decision: %s\n",
+ (GNUNET_YES == is_admitted) ? "admitted":"rejected");
+
+ result = (GNUNET_YES == is_admitted) ? GNUNET_OK : GNUNET_SYSERR;
+
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+static void
+join_request_cb (void *cls,
+ const struct GNUNET_PSYC_JoinRequestMessage *req,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key,
+ const struct GNUNET_PSYC_Message *join_msg,
+ struct GNUNET_PSYC_JoinHandle *jh)
+{
+ struct GNUNET_HashCode slave_key_hash;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "master: got join request.\n");
+
+ GNUNET_CRYPTO_hash (slave_key, sizeof (*slave_key), &slave_key_hash);
+
+ GNUNET_PSYC_join_decision (jh, GNUNET_YES, 0, NULL, NULL);
+}
+
+static void
+psyc_da ()
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "disconnect form PSYC service\n");
+}
+
+static void *
+psyc_ca (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct pctx *peer = (struct pctx*) cls;
+
+ // Case: master role
+ if (0 == peer->idx) {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connecting to PSYC as master ...\n");
+
+ peer->psyc = (struct GNUNET_PSYC_Master *)
+ GNUNET_PSYC_master_start (cfg,
+ peer->channel_key,
+ GNUNET_PSYC_CHANNEL_PRIVATE,
+ NULL,
+ join_request_cb,
+ NULL,
+ NULL,
+ cls);
+ return peer->psyc;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connecting to PSYC as slave ...\n");
+
+ struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
+ GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, "_foo", "bar baz", 7);
+ GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, "_foo_bar", "foo bar baz", 11);
+
+ struct GNUNET_PSYC_Message *
+ join_msg = GNUNET_PSYC_message_create ("_request_join", env, "some data", 40);
+
+ peer->psyc = (struct GNUNET_PSYC_Slave *)
+ GNUNET_PSYC_slave_join (cfg,
+ peer->channel_pub_key,
+ peer->id_key,
+ GNUNET_PSYC_SLAVE_JOIN_NONE,
+ peer->peer_id_master,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ join_decision_cb,
+ cls,
+ join_msg);
+
+ GNUNET_free (join_msg);
+ peer->channel = GNUNET_PSYC_slave_get_channel (peer->psyc);
+ GNUNET_PSYC_env_destroy (env);
+
+ return peer->psyc;
+}
+
+static void
+service_connect (void *cls,
+ struct GNUNET_TESTBED_Operation *op,
+ void *ca_result,
+ const char *emsg)
+{
+ GNUNET_assert (NULL != ca_result);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to the service\n");
+}
+
+static void
+connect_to_services (void *cls)
+{
+ for (int i = 0; i < 2; i++)
+ {
+ PEERS[i].peer_id_master = PEERS[0].peer_id;
+
+ op[op_cnt++] =
+ GNUNET_TESTBED_service_connect (NULL, PEERS[i].testbed_peer, "psyc",
+ &service_connect, &PEERS[i], &psyc_ca,
+ &psyc_da, &PEERS[i]);
+ }
+}
+
+static void
+pinfo_cb (void *cls,
+ struct GNUNET_TESTBED_Operation *operation,
+ const struct GNUNET_TESTBED_PeerInformation *pinfo,
+ const char *emsg)
+{
+ struct pctx *peer = (struct pctx*) cls;
+
+ peer->peer_id = pinfo->result.id;
+
+ pids++;
+ if (pids < 2)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got all IDs, starting test\n");
+
+ GNUNET_SCHEDULER_add_now (&connect_to_services, NULL);
+}
+
+static void
+testbed_master (void *cls,
+ struct GNUNET_TESTBED_RunHandle *h,
+ unsigned int num_peers,
+ struct GNUNET_TESTBED_Peer **p,
+ unsigned int links_succeeded,
+ unsigned int links_failed)
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key = NULL;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to testbed_master\n");
+
+ // Set up shutdown logic
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
+ timeout_task_id =
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 15),
+ &timeout_task, NULL);
+ GNUNET_assert (NULL != timeout_task_id);
+
+ // Set up channel key
+ channel_key = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_assert (NULL != channel_key);
+
+ // Set up information contexts for peers
+ for (int i=0 ; i < 2 ; i++)
+ {
+ PEERS[i].idx = i;
+ PEERS[i].testbed_peer = p[i];
+
+ // Create "egos"
+ PEERS[i].id_key = GNUNET_CRYPTO_ecdsa_key_create ();
+
+ // Set up channel keys shared by master and slave
+ PEERS[i].channel_key = channel_key;
+
+ PEERS[i].channel_pub_key =
+ GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ // Get public key
+ GNUNET_CRYPTO_eddsa_key_get_public (PEERS[i].channel_key,
+ PEERS[i].channel_pub_key);
+ // Get peerinfo
+ op[op_cnt++] =
+ GNUNET_TESTBED_peer_get_information (p[i],
+ GNUNET_TESTBED_PIT_IDENTITY,
+ pinfo_cb, &PEERS[i]);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ int ret;
+
+ ret = GNUNET_TESTBED_test_run ("test_psyc_api_join", "test_psyc.conf",
+ 2, 0LL, NULL, NULL,
+ &testbed_master, NULL);
+
+ if ( (GNUNET_OK != ret) || (GNUNET_OK != result) )
+ return 1;
+
+ return 0;
+}
+
+/* end of test_psyc_api_join.c */
diff --git a/src/psycstore/.gitignore b/src/psycstore/.gitignore
new file mode 100644
index 0000000..5ec7832
--- /dev/null
+++ b/src/psycstore/.gitignore
@@ -0,0 +1,5 @@
+gnunet-service-psycstore
+test_plugin_psycstore_mysql
+test_plugin_psycstore_sqlite
+test_plugin_psycstore_postgres
+test_psycstore
diff --git a/src/psycstore/Makefile.am b/src/psycstore/Makefile.am
new file mode 100644
index 0000000..557bb42
--- /dev/null
+++ b/src/psycstore/Makefile.am
@@ -0,0 +1,155 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+plugindir = $(libdir)/gnunet
+
+pkgcfgdir= $(pkgdatadir)/config.d/
+
+libexecdir= $(pkglibdir)/libexec/
+
+pkgcfg_DATA = \
+ psycstore.conf
+
+
+if MINGW
+ WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols
+endif
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+if HAVE_MYSQL
+MYSQL_PLUGIN = libgnunet_plugin_psycstore_mysql.la
+if HAVE_TESTING
+MYSQL_TESTS = test_plugin_psycstore_mysql
+endif
+endif
+
+if HAVE_POSTGRESQL
+POSTGRES_PLUGIN = libgnunet_plugin_psycstore_postgres.la
+if HAVE_TESTING
+POSTGRES_TESTS = test_plugin_psycstore_postgres
+endif
+endif
+
+if HAVE_SQLITE
+SQLITE_PLUGIN = libgnunet_plugin_psycstore_sqlite.la
+if HAVE_TESTING
+SQLITE_TESTS = test_plugin_psycstore_sqlite
+endif
+endif
+
+lib_LTLIBRARIES = libgnunetpsycstore.la
+
+libgnunetpsycstore_la_SOURCES = \
+ psycstore_api.c \
+ psycstore.h
+libgnunetpsycstore_la_LIBADD = \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(GN_LIBINTL) $(XLIB)
+libgnunetpsycstore_la_LDFLAGS = \
+ $(GN_LIB_LDFLAGS) $(WINFLAGS) \
+ -version-info 0:0:0
+
+bin_PROGRAMS =
+
+libexec_PROGRAMS = \
+ gnunet-service-psycstore
+
+gnunet_service_psycstore_SOURCES = \
+ gnunet-service-psycstore.c
+gnunet_service_psycstore_LDADD = \
+ $(top_builddir)/src/statistics/libgnunetstatistics.la \
+ $(top_builddir)/src/util/libgnunetutil.la \
+ $(top_builddir)/src/psycutil/libgnunetpsycutil.la \
+ $(GN_LIBINTL)
+
+plugin_LTLIBRARIES = \
+ $(SQLITE_PLUGIN) \
+ $(MYSQL_PLUGIN) \
+ $(POSTGRES_PLUGIN)
+
+
+libgnunet_plugin_psycstore_mysql_la_SOURCES = \
+ plugin_psycstore_mysql.c
+libgnunet_plugin_psycstore_mysql_la_LIBADD = \
+ libgnunetpsycstore.la \
+ $(top_builddir)/src/my/libgnunetmy.la \
+ $(top_builddir)/src/mysql/libgnunetmysql.la \
+ $(top_builddir)/src/statistics/libgnunetstatistics.la \
+ $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) \
+ $(LTLIBINTL)
+libgnunet_plugin_psycstore_mysql_la_LDFLAGS = \
+ $(GN_PLUGIN_LDFLAGS)
+
+libgnunet_plugin_psycstore_postgres_la_SOURCES = \
+ plugin_psycstore_postgres.c
+libgnunet_plugin_psycstore_postgres_la_LIBADD = \
+ libgnunetpsycstore.la \
+ $(top_builddir)/src/pq/libgnunetpq.la \
+ $(top_builddir)/src/statistics/libgnunetstatistics.la \
+ $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) -lpq \
+ $(LTLIBINTL)
+libgnunet_plugin_psycstore_postgres_la_LDFLAGS = \
+ $(GN_PLUGIN_LDFLAGS) $(POSTGRESQL_LDFLAGS)
+libgnunet_plugin_psycstore_postgres_la_CPPFLAGS = \
+ $(POSTGRESQL_CPPFLAGS) $(AM_CPPFLAGS)
+
+
+libgnunet_plugin_psycstore_sqlite_la_SOURCES = \
+ plugin_psycstore_sqlite.c
+libgnunet_plugin_psycstore_sqlite_la_LIBADD = \
+ libgnunetpsycstore.la \
+ $(top_builddir)/src/statistics/libgnunetstatistics.la \
+ $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) -lsqlite3 \
+ $(LTLIBINTL)
+libgnunet_plugin_psycstore_sqlite_la_LDFLAGS = \
+ $(GN_PLUGIN_LDFLAGS)
+
+
+if HAVE_SQLITE
+if HAVE_TESTING
+check_PROGRAMS = \
+ $(SQLITE_TESTS) \
+ $(MYSQL_TESTS) \
+ $(POSTGRES_TESTS) \
+ test_psycstore
+endif
+endif
+
+if ENABLE_TEST_RUN
+AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
+TESTS = $(check_PROGRAMS)
+endif
+
+test_psycstore_SOURCES = \
+ test_psycstore.c
+test_psycstore_LDADD = \
+ libgnunetpsycstore.la \
+ $(top_builddir)/src/testing/libgnunettesting.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+
+EXTRA_DIST = \
+ test_psycstore.conf
+
+
+test_plugin_psycstore_sqlite_SOURCES = \
+ test_plugin_psycstore.c
+test_plugin_psycstore_sqlite_LDADD = \
+ $(top_builddir)/src/testing/libgnunettesting.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+
+test_plugin_psycstore_mysql_SOURCES = \
+ test_plugin_psycstore.c
+test_plugin_psycstore_mysql_LDADD = \
+ $(top_builddir)/src/testing/libgnunettesting.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+
+test_plugin_psycstore_postgres_SOURCES = \
+ test_plugin_psycstore.c
+test_plugin_psycstore_postgres_LDADD = \
+ $(top_builddir)/src/testing/libgnunettesting.la \
+ $(top_builddir)/src/util/libgnunetutil.la
+
diff --git a/src/psycstore/gnunet-service-psycstore.c b/src/psycstore/gnunet-service-psycstore.c
new file mode 100644
index 0000000..9aebd3e
--- /dev/null
+++ b/src/psycstore/gnunet-service-psycstore.c
@@ -0,0 +1,1049 @@
+/**
+ * This file is part of GNUnet
+ * Copyright (C) 2013 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
+ */
+
+/**
+ * @file psycstore/gnunet-service-psycstore.c
+ * @brief PSYCstore service
+ * @author Gabor X Toth
+ * @author Christian Grothoff
+ */
+
+#include <inttypes.h>
+
+#include "platform.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_constants.h"
+#include "gnunet_protocols.h"
+#include "gnunet_statistics_service.h"
+#include "gnunet_psyc_util_lib.h"
+#include "gnunet_psycstore_service.h"
+#include "gnunet_psycstore_plugin.h"
+#include "psycstore.h"
+
+
+/**
+ * Handle to our current configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Service handle.
+ */
+static struct GNUNET_SERVICE_Handle *service;
+
+/**
+ * Handle to the statistics service.
+ */
+static struct GNUNET_STATISTICS_Handle *stats;
+
+/**
+ * Database handle
+ */
+static struct GNUNET_PSYCSTORE_PluginFunctions *db;
+
+/**
+ * Name of the database plugin
+ */
+static char *db_lib_name;
+
+
+/**
+ * Task run during shutdown.
+ *
+ * @param cls unused
+ */
+static void
+shutdown_task (void *cls)
+{
+ if (NULL != stats)
+ {
+ GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
+ stats = NULL;
+ }
+ GNUNET_break (NULL == GNUNET_PLUGIN_unload (db_lib_name, db));
+ GNUNET_free (db_lib_name);
+ db_lib_name = NULL;
+}
+
+
+/**
+ * Send a result code back to the client.
+ *
+ * @param client
+ * Client that should receive the result code.
+ * @param result_code
+ * Code to transmit.
+ * @param op_id
+ * Operation ID in network byte order.
+ * @param err_msg
+ * Error message to include (or NULL for none).
+ */
+static void
+send_result_code (struct GNUNET_SERVICE_Client *client,
+ uint64_t op_id,
+ int64_t result_code,
+ const char *err_msg)
+{
+ struct OperationResult *res;
+ size_t err_size = 0;
+
+ if (NULL != err_msg)
+ err_size = strnlen (err_msg,
+ GNUNET_MAX_MESSAGE_SIZE - sizeof (*res) - 1) + 1;
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (res, err_size,
+ GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_CODE);
+ res->result_code = GNUNET_htonll (result_code - INT64_MIN);
+ res->op_id = op_id;
+ if (0 < err_size)
+ {
+ GNUNET_memcpy (&res[1], err_msg, err_size);
+ ((char *) &res[1])[err_size - 1] = '\0';
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending result to client: %" PRId64 " (%s)\n",
+ result_code, err_msg);
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
+}
+
+
+enum
+{
+ MEMBERSHIP_TEST_NOT_NEEDED = 0,
+ MEMBERSHIP_TEST_NEEDED = 1,
+ MEMBERSHIP_TEST_DONE = 2,
+} MessageMembershipTest;
+
+
+struct SendClosure
+{
+ struct GNUNET_SERVICE_Client *client;
+
+ /**
+ * Channel's public key.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey channel_key;
+
+ /**
+ * Slave's public key.
+ */
+ struct GNUNET_CRYPTO_EcdsaPublicKey slave_key;
+
+ /**
+ * Operation ID.
+ */
+ uint64_t op_id;
+
+ /**
+ * Membership test result.
+ */
+ int membership_test_result;
+
+ /**
+ * Do membership test with @a slave_key before returning fragment?
+ * @see enum MessageMembershipTest
+ */
+ uint8_t membership_test;
+};
+
+
+static int
+send_fragment (void *cls, struct GNUNET_MULTICAST_MessageHeader *msg,
+ enum GNUNET_PSYCSTORE_MessageFlags flags)
+{
+ struct SendClosure *sc = cls;
+ struct FragmentResult *res;
+
+ if (MEMBERSHIP_TEST_NEEDED == sc->membership_test)
+ {
+ sc->membership_test = MEMBERSHIP_TEST_DONE;
+ sc->membership_test_result
+ = db->membership_test (db->cls, &sc->channel_key, &sc->slave_key,
+ GNUNET_ntohll (msg->message_id));
+ switch (sc->membership_test_result)
+ {
+ case GNUNET_YES:
+ break;
+
+ case GNUNET_NO:
+ case GNUNET_SYSERR:
+ return GNUNET_NO;
+ }
+ }
+
+ size_t msg_size = ntohs (msg->header.size);
+
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (res, msg_size,
+ GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_FRAGMENT);
+ res->op_id = sc->op_id;
+ res->psycstore_flags = htonl (flags);
+ GNUNET_memcpy (&res[1], msg, msg_size);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending fragment %llu to client\n",
+ (unsigned long long) GNUNET_ntohll (msg->fragment_id));
+ GNUNET_free (msg);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (sc->client), env);
+ return GNUNET_YES;
+}
+
+
+static int
+send_state_var (void *cls, const char *name,
+ const void *value, uint32_t value_size)
+{
+ struct SendClosure *sc = cls;
+ struct StateResult *res;
+ size_t name_size = strlen (name) + 1;
+
+ /** @todo FIXME: split up value into 64k chunks */
+
+ struct GNUNET_MQ_Envelope *
+ env = GNUNET_MQ_msg_extra (res, name_size + value_size,
+ GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_STATE);
+ res->op_id = sc->op_id;
+ res->name_size = htons (name_size);
+ GNUNET_memcpy (&res[1], name, name_size);
+ GNUNET_memcpy ((char *) &res[1] + name_size, value, value_size);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Sending state variable %s to client\n", name);
+
+ GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (sc->client), env);
+ return GNUNET_OK;
+}
+
+
+static void
+handle_client_membership_store (void *cls,
+ const struct MembershipStoreRequest *req)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+
+ int ret = db->membership_store (db->cls, &req->channel_key, &req->slave_key,
+ req->did_join,
+ GNUNET_ntohll (req->announced_at),
+ GNUNET_ntohll (req->effective_since),
+ GNUNET_ntohll (req->group_generation));
+
+ if (ret != GNUNET_OK)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to store membership information!\n"));
+
+ send_result_code (client, req->op_id, ret, NULL);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static void
+handle_client_membership_test (void *cls,
+ const struct MembershipTestRequest *req)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+
+ int ret = db->membership_test (db->cls, &req->channel_key, &req->slave_key,
+ GNUNET_ntohll (req->message_id));
+ switch (ret)
+ {
+ case GNUNET_YES:
+ case GNUNET_NO:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to test membership!\n"));
+ }
+
+ send_result_code (client, req->op_id, ret, NULL);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_fragment_store (void *cls,
+ const struct FragmentStoreRequest *req)
+{
+ return GNUNET_OK;
+}
+
+
+static void
+handle_client_fragment_store (void *cls,
+ const struct FragmentStoreRequest *req)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+
+ const struct GNUNET_MessageHeader *
+ msg = GNUNET_MQ_extract_nested_mh (req);
+ if (NULL == msg
+ || ntohs (msg->size) < sizeof (struct GNUNET_MULTICAST_MessageHeader))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Dropping invalid fragment\n"));
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ int ret = db->fragment_store (db->cls, &req->channel_key,
+ (const struct GNUNET_MULTICAST_MessageHeader *)
+ msg, ntohl (req->psycstore_flags));
+
+ if (ret != GNUNET_OK)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to store fragment\n"));
+
+ send_result_code (client, req->op_id, ret, NULL);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static void
+handle_client_fragment_get (void *cls,
+ const struct FragmentGetRequest *req)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+
+ struct SendClosure
+ sc = { .op_id = req->op_id,
+ .client = client,
+ .channel_key = req->channel_key,
+ .slave_key = req->slave_key,
+ .membership_test = req->do_membership_test };
+
+ int64_t ret;
+ uint64_t ret_frags = 0;
+ uint64_t first_fragment_id = GNUNET_ntohll (req->first_fragment_id);
+ uint64_t last_fragment_id = GNUNET_ntohll (req->last_fragment_id);
+ uint64_t limit = GNUNET_ntohll (req->fragment_limit);
+
+ if (0 == limit)
+ ret = db->fragment_get (db->cls, &req->channel_key,
+ first_fragment_id, last_fragment_id,
+ &ret_frags, send_fragment, &sc);
+ else
+ ret = db->fragment_get_latest (db->cls, &req->channel_key, limit,
+ &ret_frags, send_fragment, &sc);
+
+ switch (ret)
+ {
+ case GNUNET_YES:
+ case GNUNET_NO:
+ if (MEMBERSHIP_TEST_DONE == sc.membership_test)
+ {
+ switch (sc.membership_test_result)
+ {
+ case GNUNET_YES:
+ break;
+
+ case GNUNET_NO:
+ ret = GNUNET_PSYCSTORE_MEMBERSHIP_TEST_FAILED;
+ break;
+
+ case GNUNET_SYSERR:
+ ret = GNUNET_SYSERR;
+ break;
+ }
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to get fragment!\n"));
+ }
+ send_result_code (client, req->op_id, (ret < 0) ? ret : ret_frags, NULL);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static int
+check_client_message_get (void *cls,
+ const struct MessageGetRequest *req)
+{
+ return GNUNET_OK;
+}
+
+
+static void
+handle_client_message_get (void *cls,
+ const struct MessageGetRequest *req)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+
+ uint16_t size = ntohs (req->header.size);
+ const char *method_prefix = (const char *) &req[1];
+
+ if (size < sizeof (*req) + 1
+ || '\0' != method_prefix[size - sizeof (*req) - 1])
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Message get: invalid method prefix. size: %u < %u?\n",
+ size,
+ (unsigned int) (sizeof (*req) + 1));
+ GNUNET_break (0);
+ GNUNET_SERVICE_client_drop (client);
+ return;
+ }
+
+ struct SendClosure
+ sc = { .op_id = req->op_id,
+ .client = client,
+ .channel_key = req->channel_key,
+ .slave_key = req->slave_key,
+ .membership_test = req->do_membership_test };
+
+ int64_t ret;
+ uint64_t ret_frags = 0;
+ uint64_t first_message_id = GNUNET_ntohll (req->first_message_id);
+ uint64_t last_message_id = GNUNET_ntohll (req->last_message_id);
+ uint64_t msg_limit = GNUNET_ntohll (req->message_limit);
+ uint64_t frag_limit = GNUNET_ntohll (req->fragment_limit);
+
+ /** @todo method_prefix */
+ if (0 == msg_limit)
+ ret = db->message_get (db->cls, &req->channel_key,
+ first_message_id, last_message_id, frag_limit,
+ &ret_frags, send_fragment, &sc);
+ else
+ ret = db->message_get_latest (db->cls, &req->channel_key, msg_limit,
+ &ret_frags, send_fragment, &sc);
+
+ switch (ret)
+ {
+ case GNUNET_YES:
+ case GNUNET_NO:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to get message!\n"));
+ }
+
+ send_result_code (client, req->op_id, (ret < 0) ? ret : ret_frags, NULL);
+ GNUNET_SERVICE_client_continue (client);
+}
+
+
+static void
+handle_client_message_get_fragment (void *cls,
+ const struct MessageGetFragmentRequest *req)
+{
+ struct GNUNET_SERVICE_Client *client = cls;
+
+ struct SendClosure
+ sc = { .op_id = req->op_id, .client = client,
+ .channel_key = req->channel_key, .slave_key = req->slave_key,
+ .membership_test = req->do_membership_test };
+
+ int ret = db->message_get_fragment (db->cls, &req->channel_key,
+ GNUNET_ntohll (req->message_id),
+ GNUNET_ntohll (req->fragment_offset),
+ &send_fragment, &sc);
+ switch (ret)
+ {
+ case GNUNET_YES:
+ case GNUNET_NO:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to get messa