diff options
Diffstat (limited to 'src')
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, + ¬ify, + 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 message fragment!\n")); + } + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_counters_get (void *cls, + const struct OperationRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + struct CountersResult *res; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_COUNTERS); + + int ret = db->counters_message_get (db->cls, &req->channel_key, + & |