From 70eb6f1e9f1df73e5dfbe0401c848678ebbbabab Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 7 Apr 2013 20:51:10 +0000 Subject: -first thoughs on a PSYC API, not finished --- src/include/gnunet_multicast_service.h | 5 +- src/include/gnunet_psyc_service.h | 677 +++++++++++++++++++++++++++++++++ 2 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 src/include/gnunet_psyc_service.h (limited to 'src') diff --git a/src/include/gnunet_multicast_service.h b/src/include/gnunet_multicast_service.h index cf99d764e..133af9b8f 100644 --- a/src/include/gnunet_multicast_service.h +++ b/src/include/gnunet_multicast_service.h @@ -311,7 +311,7 @@ typedef void (*GNUNET_MULTICAST_MessageCallback) (void *cls, struct GNUNET_MULTICAST_Origin * GNUNET_MULTICAST_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg, void *cls, - struct GNUNET_CRYPTO_EccPrivateKey *priv_key, + const struct GNUNET_CRYPTO_EccPrivateKey *priv_key, enum GNUNET_MULTICAST_JoinPolicy join_policy, GNUNET_MULITCAST_ReplayCallback replay_cb, GNUNET_MULITCAST_MembershipTestCallback test_cb, @@ -324,6 +324,7 @@ GNUNET_MULTICAST_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg, * * @param origin handle to the multicast group * @param msg_body body of the message to transmit + * FIXME: change to notify_transmit_ready-style to wait for ACKs? */ void GNUNET_MULTICAST_origin_send_to_all (struct GNUNET_MULTICAST_Origin *origin, @@ -370,7 +371,7 @@ GNUNET_MULTICAST_origin_end (struct GNUNET_MULTICAST_Origin *origin); struct GNUNET_MULTICAST_Member * GNUNET_MULTICAST_member_join (const struct GNUNET_CONFIGURATION_Handle *cfg, void *cls, - struct GNUNET_CRYPTO_EccPublicKey *pub_key, + const struct GNUNET_CRYPTO_EccPublicKey *pub_key, uint64_t max_known_message_id, uint64_t max_known_state_message_id, GNUNET_MULTICAST_ReplayCallback replay_cb, diff --git a/src/include/gnunet_psyc_service.h b/src/include/gnunet_psyc_service.h new file mode 100644 index 000000000..84b8ada39 --- /dev/null +++ b/src/include/gnunet_psyc_service.h @@ -0,0 +1,677 @@ +/* + This file is part of GNUnet. + (C) 2012, 2013 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file include/gnunet_psyc_service.h + * @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! + * @author Christian Grothoff + * + * TODO: + * - how to deal with very large channel state (i.e. channel + * containing a movie); this might relate to the question + * of how (when/etc.) we replay method calls; is only the + * channel state persistent? What about a 'bounded' + * channel history, how would we enable that? + * - how to deal with seeking in large channel state (i.e. + * skip to minute 45 in movie) + * - need to change send operations to 'notify_transmit_ready'-style; + * deal better with 'streaming' arguments while we're at it + */ + +#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" + + +/** + * Version number of GNUnet-PSYC API. + */ +#define GNUNET_PSYC_VERSION 0x00000000 + + +/** + * Bits describing special properties of arguments. + */ +enum GNUNET_PSYC_ArgumentFlags +{ + /** + * Argument is fixed size. + */ + GNUNET_PSYC_AF_FIXED_SIZE = 0, + + /** + * Argument is variable-length + */ + GNUNET_PSYC_AF_VARIABLE_SIZE = 1, + + /** + * Argument may be supplied incrementally to the callback + */ + GNUNET_PSYC_AF_STREAMABLE = 2, + + /** + * Argument is variable-length, incrementally supplied + * data stream. + */ + GNUNET_PSYC_AF_STREAM = 3, + + /** + * Argument is zero-terminated character array. + */ + GNUNET_PSYC_AF_ZERO_TERMINATED_CHARARRAY = 4, + + /** + * Argument is variable-length UTF-8 encoded, zero-terminated string. + */ + GNUNET_PSYC_AF_UTF8 = 5, + + /** + * Payload is an unsigned integer and might thus be encoded as an + * integer when generating PSYC stream (useful if we want to + * generate human-readable PSYC streams, instead of just always + * using length-prefixed binary encodings). Note that it + * is not sufficient to just test for this bit, as it is + * also set for 'REAL' numbers! + */ + GNUNET_PSYC_AF_UNSIGNED_INTEGER = 8, + + /** + * Payload is an unsigned integer and might thus be encoded as an + * integer when generating PSYC stream (useful if we want to + * generate human-readable PSYC streams, instead of just always + * using length-prefixed binary encodings). Note that it + * is not sufficient to just test for this bit, as it is + * also set for 'REAL' numbers! + */ + GNUNET_PSYC_AF_SIGNED_INTEGER = 16, + + /** + * Payload is a 'real' number (float or double). We save a bit here + * as a number cannot be both SIGNED and UNSIGNED, so setting both + * bits is fine to use for REALs. + */ + GNUNET_PSYC_AF_REAL_NUMBER = 24 + +}; + + +/** + * Argument descriptors are used to describe types that can be + * embedded in a PSYC stream. For example, a "uint32_t" is + * described as 4-byte, fixed-length data, whereas a movie + * would be a variable-size, streaming argument. + */ +struct GNUNET_PSYC_ArgumentDescriptor +{ + + /** + * Required length of the argument in bytes, zero for + * variable-size arguments. + */ + size_t arg_len; + + /** + * Flags describing additional properties of the argument, + * such as variable-size, streaming or 0-termination. This + * argument is a bitfield. + */ + enum GNUNET_PSYC_ArgumentFlags flags; + +}; + + +/** + * Convenience macro to define an argument descriptor for + * some fixed-size C data type. + * + * @param pt C data type (i.e. 'uint32_t') + */ +#define GNUNET_PSYC_AD_C_TYPE(pt) { sizeof (pt), GNUNET_PSYC_AF_FIXED_SIZE } + +/** + * Convenience macro to define an argument descriptor for + * some fixed-size unsigned integer type. + * + * @param it C integer data type (i.e. 'uint32_t') + */ +#define GNUNET_PSYC_AD_C_UINT_TYPE(it) { sizeof (it), GNUNET_PSYC_AF_FIXED_SIZE | GNUNET_PSYC_AF_UNSIGNED_INTEGER } + +/** + * Argument descriptor for a 'uint8_t' argument. + */ +#define GNUNET_PSYC_AD_UINT8 GNUNET_PSYC_AD_C_UINT_TYPE(uint8_t) + +/** + * Argument descriptor for a 'uint16_t' argument. + */ +#define GNUNET_PSYC_AD_UINT16 GNUNET_PSYC_AD_C_UINT_TYPE(uint16_t) + +/** + * Argument descriptor for a 'uint32_t' argument. + */ +#define GNUNET_PSYC_AD_UINT32 GNUNET_PSYC_AD_C_UINT_TYPE(uint32_t) + +/** + * Argument descriptor for a 'uint64_t' argument. + */ +#define GNUNET_PSYC_AD_UINT64 GNUNET_PSYC_AD_C_UINT_TYPE(uint64_t) + +/** + * Convenience macro to define an argument descriptor for + * a 0-terminated, variable-length UTF-8 string. + */ +#define GNUNET_PSYC_AD_UTF8 { 0, GNUNET_PSYC_AF_UTF8 } + + +/* TODO: add more convenience macros for argument types later as needed */ + + +/** + * Abstract argument passed to a GNUNET_PSYC_Method. + */ +struct GNUNET_PSYC_Argument +{ + + /** + * Data of the argument. + */ + const void *data; + + /** + * Number of bytes in 'data', guaranteed to be the argument + * descriptor 'arg_len' MINUS 'data_off' unless + * GNUNET_PSYC_AF_VARIABLE_SIZE was set. + */ + size_t data_size; + + /** + * Offset of 'data' in the overall argument, + * always zero unless GNUNET_PSYC_AF_STREAMABLE was + * set for the argument. + */ + uint64_t data_off; + + /** + * Total number of bytes to be expected in 'data', + * UINT64_MAX for 'unknown' (i.e. for "infinite" + * streams). + */ + uint64_t value_size; +}; + + +/** + * Method called from PSYC upon receiving a message indicating a call + * to a 'method'. The arguments given will match those of the + * respective argument descriptor. If some arguments were marked + * as 'streaming', the function can return a value other than -1 + * to request "more" of the data for that argument. Note that all + * non-streaming arguments will be replayed in full for each additional + * invocation of the method. Using more than one streaming argument + * is possible, in which case PSYC will ONLY advance the stream of the + * argument for which the index was returned; the values of other + * streaming arguments will be replayed at the current offset. + * + * Returning a value other than -1 or that of a streaming argument is + * not allowed. Returning -1 does not indicate an error; it simply + * indicates that the client wants to proceed with the next method + * (and not see the rest of the data from any of the streaming + * arguments). + * + * TODO: note that this API currently doesn't allow for seeking + * in streaming data (very advanced feature) + * + * @param cls closure + * @param full_method_name original method name from PSYC (may be more + * specific than the registered method name due to try&slice matching) + * @param sender who transmitted the message (origin, except for messages + * from one of the members to the origin) + * @param message_id unique message counter for this message + * @param group_generation group generation counter for this message + * @param argc number of arguments in argv + * @param argv array of argc arguments to the method + * @return -1 if we're finished with this method, index + * of a streaming argument for which more data is + * requested otherwise + */ +typedef int (*GNUNET_PSYC_Method)(void *cls, + const char *full_method_name, + const struct GNUNET_PeerIdentity *sender, + unsigned int argc, + const struct GNUNET_PSYC_Argument *argv); + + +/** + * Descriptor for a PSYC method and its arguments. Here is how this + * is expected to be used. Imagine we have a method with the + * following signature: + *
+ * static void 
+ * logger (void *cls, uint32_t log_level, const char *log_message);
+ * 
+ * where 'cls' is supposed to be a 'FILE *'. + * Then for PSYC to call this method with 'stderr' for 'cls', + * we would provide the following method descriptor: + *
+ * .method_name = "log";
+ * .method = &wrap_logger;
+ * .method_cls = stderr;
+ * .argc = 2;
+ * .argv = { GNUNET_PSYC_AD_UINT32, GNUNET_PSYC_AD_UTF8 };
+ * 
+ * and define wrap_logger as follows: + *
+ * static void
+ * wrap_logger (void *cls, const char full_method_name, 
+ *              const struct GNUNET_PeerIdentity *sender,
+ *    	        unsigned int argc, const struct GNUNET_PSYC_Argument *argv)
+ * {
+ *    uint32_t *log_level = argv[0].data;
+ *    const char *log_message = argv[1].data;
+ *    logger (cls, *log_level, log_message);
+ * }
+ * 
+ * Note that the PSYC library will take care of making sure + * that 'argv[0].data_size == 4' and that the log message + * is 0-terminated, as those requirements were specified + * in the method descriptor for those arguments. Finally, + * it is conceivable to generate the wrappers and method + * descriptors automatically, as they are trivial. + *

+ * Note that due to try & slice, the given full method name + * might be more specific; for example, the given method + * might be called for a request to "log_warning" instead + * of just a request to "log". + */ +struct GNUNET_PSYC_MethodDescriptor +{ + + /** + * Name of the method to be used in try-and-slice matching. + */ + const char *method_name; + + /** + * Function to call. Note that if a more specific handler exists + * as well, the more generic handler will not be invoked. + */ + GNUNET_PSYC_Method method; + + /** + * Closure for the method (this argument and the 'sender' argument + * are both not included in 'argc'). + */ + void *method_cls; + + /** + * Number of arguments to pass to the method (length of the 'ads' + * array). + */ + unsigned int argc; + + /** + * Array of 'argc' argument descriptors describing the arguments to + * be passed to the method. Non-matching method calls will be + * ignored (but logged). Note that the 'ads' of all methods with + * the same method name prefix should be identical. + */ + const struct GNUNET_PSYC_ArgumentDescriptor *ads; + +}; + + +/** + * Handle for the origin of a psyc group. + */ +struct GNUNET_PSYC_Origin; + + +/** + * Start a psyc group. Will create a multicast group identified by + * the given public key. Messages recevied 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 origin can also change group membership + * without explicit requests. Note that PSYC doesn't itself "understand" + * join or leave 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 method_count number of methods in 'methods' array + * @param methods functions to invoke on messages received from members, + * typcially at least contains functions for 'join' and 'leave'. + * @param priv_key ECC key that will be used to sign messages for this + * psyc session; public key is used to identify the + * psyc group; FIXME: we'll likely want to use + * NOT the p521 curve here, but a cheaper one in the future + * @param join_policy what is the membership policy of the group? + * @return handle for the origin, NULL on error + */ +struct GNUNET_PSYC_Origin * +GNUNET_PSYC_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + unsigned int method_count, + const struct GNUNET_PSYC_MethodDescriptor *methods, + const struct GNUNET_CRYPTO_EccPrivateKey *priv_key, + enum GNUNET_MULTICAST_JoinPolicy join_policy); + + +/** + * Update channel state. The state of a channel must fit into the + * memory of each member (and the origin); large values that require + * streaming must only be passed as streaming arguments to methods. + * State updates might not be transmitted to group members until + * the next call to 'GNUNET_PSYC_origin_broadcast_call_method'. + * + * @param origin handle to the psyc group / channel + * @param full_state_name name of the field in the channel state to change + * @param data_size number of bytes in data + * @param data new state value + */ +void +GNUNET_PSYC_origin_update_state (struct GNUNET_PSYC_Origin *origin, + const char *full_state_name, + size_t data_size, + const void *data); + + +/** + * Data needed to construct a PSYC message to call a method. + */ +struct GNUNET_PSYC_CallData +{ + + /** + * Name of the function to call. This name may be more specific + * than the registered method name due to try&slice matching. + */ + const char *full_method_name; + + /** + * Number of arguments to pass (other than closure and sender), + * length of the 'argv' array. + */ + unsigned int argc; + + /** + * Arguments to pass to the function. + */ + const struct GNUNET_PSYC_Argument *argv; + +}; + + +/** + * Send a message to call a method to all members in the psyc group. + * + * @param origin handle to the psyc group + * @param increment_group_generation GNUNET_YES if we need to increment + * the group generation counter after transmitting this message + * @param call_data data needed to determine how to call which method + * @param message_id set to the unique message ID that was generated for + * this message + * @param group_generation set to the group generation used for this + * message + * FIXME: change to notify_transmit_ready-style to wait for ACKs? + * that'd also help with streaming arguments! + * => need to change multicast API first as well! + */ +void +GNUNET_PSYC_origin_broadcast_call_method (struct GNUNET_PSYC_Origin *origin, + int increment_group_generation, + const struct GNUNET_PSYC_CallData *call_data, + uint64_t *message_id, + uint64_t *group_generation); + + +/** + * End a psyc group. + * + * @param origin psyc group to terminate + */ +void +GNUNET_PSYC_origin_end (struct GNUNET_PSYC_Origin *origin); + + +/** + * Handle to access PSYC group operations for all members. + */ +struct GNUNET_PSYC_Group; + + +/** + * Convert 'origin' to a 'group' handle to access the 'group' APIs. + * + * @param origin origin handle + * @return group handle, valid for as long as 'origin' is valid + */ +struct GNUNET_PSYC_Group * +GNUNET_PSYC_origin_get_group (struct GNUNET_PSYC_Origin *origin); + + +/** + * Add a member to the group. Note that this will NOT generate any + * PSYC traffic, it will merely update the local data base to modify + * how we react to 'membership test' queries. The origin still needs to + * explicitly transmit a 'leave' message to notify other group members + * and they then also must still call this function in their respective + * methods handling the 'leave' message. This way, how 'join' and 'leave' + * operations are exactly implemented is still up to the application; + * for example, there might be a 'leave_all' message to kick out everyone. + * + * Note that group members are explicitly trusted to perform these + * operations correctly; not doing so correctly will result in either + * denying members access or offering access to group data to + * non-members. + * + * @param group group handle + * @param member which peer to add + * @param message_id message ID for the message that changed the membership + * @param group_generation the generation ID where the change went into effect + */ +void +GNUNET_PSYC_group_member_admit (struct GNUNET_PSYC_Group *group, + const struct GNUNET_PeerIdentity *member, + uint64_t message_id, + uint64_t group_generation); + + +/** + * Remove a member from the group. Note that this will NOT generate any + * PSYC traffic, it will merely update the local data base to modify + * how we react to 'membership test' queries. The origin still needs to + * explicitly transmit a 'leave' message to notify other group members + * and they then also must still call this function in their respective + * methods handling the 'leave' message. This way, how 'join' and 'leave' + * operations are exactly implemented is still up to the application; + * for example, there might be a 'leave_all' message to kick out everyone. + * + * Note that group members are explicitly trusted to perform these + * operations correctly; not doing so correctly will result in either + * denying members access or offering access to group data to + * non-members. + * + * @param group group handle + * @param member which peer to remove + * @param message_id message ID for the message that changed the membership + * @param group_generation the generation ID where the change went into effect + */ +void +GNUNET_PSYC_group_member_kick (struct GNUNET_PSYC_Group *group, + const struct GNUNET_PeerIdentity *member, + uint64_t message_id, + uint64_t group_generation); + + +/** + * Function called to inform a member about state values for a channel. + * + * @param cls closure + * @param full_state_name full name of the state + * @param data_size number of bytes in 'data' + * @param data raw data of the state + */ +typedef void (*GNUNET_PSYC_StateCallback)(void *cls, + const char *full_state_name, + size_t data_size, + const void *data); + + +/** + * Descriptor for an event handler handling PSYC state updates. + */ +struct GNUNET_PSYC_StateHandler +{ + + /** + * Name of the state this handler calls about, used in try-and-slice matching. + */ + const char *state_name; + + /** + * Function to call whenever the respective state changes. + */ + GNUNET_PSYC_StateCallback event_handler; + + /** + * Closure for the 'event_handler' function. + */ + void *event_handler_cls; + + /** + * Description of the kind of state that the handler expects to see. + * Non-matching state updates will be ignored (but logged). Note + * that the state_types of all states with the same state name prefix + * should be identical. + */ + struct GNUNET_PSYC_ArgumentDescriptor state_type; + +}; + + +/** + * Join a psyc group. The entity joining is always the local peer. + * This will send a 'join_msg' to the origin; if it succeeds, the + * channel state will be replayed to the joining member and the 'join' + * method will be invoked to show that we joined successfully. There + * is no explicit notification on failure (as the origin may simply + * take days to approve, and disapproval is simply being ignored). + * + * Note that we also specify the message to transmit to origin on + * 'leave' here, as a sudden crash might otherwise not permit sending + * a 'nice' leave message. TODO: we might want an API to change + * the 'leave' message later during the session. + * + * @param cfg configuration to use + * @param pub_key ECC key that identifies the group + * @param method_count number of methods in 'methods' array + * @param methods functions to invoke on messages received from the origin, + * typcially at least contains functions for 'join' and 'leave'. + * @param state_count number of state handlers + * @param state_handlers array of state event handlers + * @param join_data method to invoke on origin to trigger joining; + * use NULL to send nothing (useful for anonymous groups that permit anyone); + arguments to give to join method, must not include streaming args + * @param leave_data method to invoke on origin on leaving; + * use NULL to send nothing (useful for anonymous groups that permit anyone); + arguments to give to leave method, must not include streaming args + * @return handle for the member, NULL on error + */ +struct GNUNET_PSYC_Member * +GNUNET_PSYC_member_join (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EccPublicKey *pub_key, + unsigned int method_count, + const struct GNUNET_PSYC_MethodDescriptor *methods, + unsigned int state_count, + struct GNUNET_PSYC_StateHandler *state_handlers, + const struct GNUNET_PSYC_CallData *join_data, + const struct GNUNET_PSYC_CallData *leave_data); + + +/** + * Request a message to be send to the origin. + * + * @param member membership handle + * @param request_data which method should be invoked on origin (and how) + * + * FIXME: change to notify_transmit_ready-style to wait for ACKs + * and to enable streaming arguments! + */ +void +GNUNET_PSYC_member_send_to_origin (struct GNUNET_PSYC_Member *member, + const struct GNUNET_PSYC_CallData *request_data); + + +/** + * Call the given state callback on all matching states in the channel + * state. The callback is invoked synchronously on all matching + * states (as the state is fully replicated in the library in this + * process; channel states should be small, large data is to be passed + * as streaming data to methods). + * + * @param member membership handle + * @param state_name name of the state to query (full name + * might be longer, this is only the prefix that must match) + * @param cb function to call on the matching state values + * @param cb_cls closure for 'cb' + */ +int +GNUNET_PSYC_member_state_get (struct GNUNET_PSYC_Member *member, + const char *state_name, + GNUNET_PSYC_StateCallback cb, + void *cb_cls); + + +/** + * Leave a mutlicast group. Will terminate the connection to the PSYC + * service, which will send the 'leave' method that was prepared + * earlier to the origin. This function must not be called on a + * 'member' that was obtained from GNUNET_PSYC_origin_get_group. + * + * @param member membership handle + */ +void +GNUNET_PSYC_member_leave (struct GNUNET_PSYC_Member *member); + + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYC_SERVICE_H */ +#endif +/* end of gnunet_psyc_service.h */ -- cgit v1.2.3