/*
      This file is part of GNUnet
      Copyright (C) 2013, 2014, 2020 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 Florian Dold
 * @author Christian Grothoff
 *
 * @file
 * Two-peer set intersection operations
 *
 * @defgroup set  Set intersection service
 * Two-peer set operations
 *
 * @{
 */

#ifndef GNUNET_SETI_SERVICE_H
#define GNUNET_SETI_SERVICE_H

#ifdef __cplusplus
extern "C"
{
#if 0                           /* keep Emacsens' auto-indent happy */
}
#endif
#endif

#include "gnunet_common.h"
#include "gnunet_time_lib.h"
#include "gnunet_configuration_lib.h"


/**
 * Maximum size of a context message for set operation requests.
 */
#define GNUNET_SETI_CONTEXT_MESSAGE_MAX_SIZE ((1 << 16) - 1024)

/**
 * Opaque handle to a set.
 */
struct GNUNET_SETI_Handle;

/**
 * Opaque handle to a set operation request from another peer.
 */
struct GNUNET_SETI_Request;

/**
 * Opaque handle to a listen operation.
 */
struct GNUNET_SETI_ListenHandle;

/**
 * Opaque handle to a set operation.
 */
struct GNUNET_SETI_OperationHandle;


/**
 * Status for the result callback
 */
enum GNUNET_SETI_Status
{

  /**
   * Element should be added to the result set of the local peer, i.e. the
   * element is in the intersection.
   */
  GNUNET_SETI_STATUS_ADD_LOCAL,

  /**
   * Element should be delete from the result set of the local peer, i.e. the
   * local peer is having an element that is not in the intersection.
   */
  GNUNET_SETI_STATUS_DEL_LOCAL,

  /**
   * The other peer refused to do the operation with us, or something went
   * wrong.
   */
  GNUNET_SETI_STATUS_FAILURE,

  /**
   * Success, all elements have been sent (and received).
   */
  GNUNET_SETI_STATUS_DONE
};


/**
 * Element stored in a set.
 */
struct GNUNET_SETI_Element
{
  /**
   * Number of bytes in the buffer pointed to by data.
   */
  uint16_t size;

  /**
   * Application-specific element type.
   */
  uint16_t element_type;

  /**
   * Actual data of the element
   */
  const void *data;
};


/**
 * Possible options to pass to a set operation.
 *
 * Used as tag for struct #GNUNET_SETI_Option.
 */
enum GNUNET_SETI_OptionType
{
  /**
   * List terminator.
   */
  GNUNET_SETI_OPTION_END = 0,

  /**
   * Return the elements remaining in the intersection
   * (#GNUNET_SETI_STATUS_ADD_LOCAL). If not given, the default is to return a
   * list of the elements to be removed (#GNUNET_SETI_STATUS_DEL_LOCAL).
   */
  GNUNET_SETI_OPTION_RETURN_INTERSECTION = 1,
};


/**
 * Option for set operations.
 */
struct GNUNET_SETI_Option
{
  /**
   * Type of the option.
   */
  enum GNUNET_SETI_OptionType type;

  /**
   * Value for the option, only used with some options.
   */
  union
  {
    uint64_t num;
  } v;
};


/**
 * Callback for set union operation results. Called for each element
 * in the result set.
 *
 * @param cls closure
 * @param element a result element, only valid if status is #GNUNET_SETI_STATUS_OK
 * @param current_size current set size
 * @param status see `enum GNUNET_SETI_Status`
 */
typedef void
(*GNUNET_SETI_ResultIterator) (void *cls,
                               const struct GNUNET_SETI_Element *element,
                               uint64_t current_size,
                               enum GNUNET_SETI_Status status);


/**
 * Called when another peer wants to do a set operation with the
 * local peer. If a listen error occurs, the @a request is NULL.
 *
 * @param cls closure
 * @param other_peer the other peer
 * @param context_msg message with application specific information from
 *        the other peer
 * @param request request from the other peer (never NULL), use GNUNET_SETI_accept()
 *        to accept it, otherwise the request will be refused
 *        Note that we can't just return value from the listen callback,
 *        as it is also necessary to specify the set we want to do the
 *        operation with, whith sometimes can be derived from the context
 *        message. It's necessary to specify the timeout.
 */
typedef void
(*GNUNET_SETI_ListenCallback) (void *cls,
                               const struct GNUNET_PeerIdentity *other_peer,
                               const struct GNUNET_MessageHeader *context_msg,
                               struct GNUNET_SETI_Request *request);


/**
 * Create an empty set, supporting the specified operation.
 *
 * @param cfg configuration to use for connecting to the
 *        set service
 * @return a handle to the set
 */
struct GNUNET_SETI_Handle *
GNUNET_SETI_create (const struct GNUNET_CONFIGURATION_Handle *cfg);


/**
 * Add an element to the given set.
 *
 * @param set set to add element to
 * @param element element to add to the set
 * @param cb function to call when finished, can be NULL
 * @param cb_cls closure for @a cb
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if the
 *         set is invalid (e.g. the set service crashed)
 */
int
GNUNET_SETI_add_element (struct GNUNET_SETI_Handle *set,
                         const struct GNUNET_SETI_Element *element,
                         GNUNET_SCHEDULER_TaskCallback cb,
                         void *cb_cls);


/**
 * Destroy the set handle, and free all associated resources.  Operations may
 * still be pending when a set is destroyed (and will be allowed to complete).
 *
 * @param set set to destroy
 */
void
GNUNET_SETI_destroy (struct GNUNET_SETI_Handle *set);


/**
 * Prepare a set operation to be evaluated with another peer.  The evaluation
 * will not start until the client provides a local set with
 * GNUNET_SETI_commit().
 *
 * @param other_peer peer with the other set
 * @param app_id hash for the application using the set
 * @param context_msg additional information for the request
 * @param options options to use when processing the request
 * @param result_cb called on error or success
 * @param result_cls closure for @a result_cb
 * @return a handle to cancel the operation
 */
struct GNUNET_SETI_OperationHandle *
GNUNET_SETI_prepare (const struct GNUNET_PeerIdentity *other_peer,
                     const struct GNUNET_HashCode *app_id,
                     const struct GNUNET_MessageHeader *context_msg,
                     const struct GNUNET_SETI_Option options[],
                     GNUNET_SETI_ResultIterator result_cb,
                     void *result_cls);


/**
 * Wait for set operation requests for the given application ID.
 * If the connection to the set service is lost, the listener is
 * re-created transparently with exponential backoff.
 *
 * @param cfg configuration to use for connecting to
 *            the set service
 * @param app_id id of the application that handles set operation requests
 * @param listen_cb called for each incoming request matching the operation
 *                  and application id
 * @param listen_cls handle for @a listen_cb
 * @return a handle that can be used to cancel the listen operation
 */
struct GNUNET_SETI_ListenHandle *
GNUNET_SETI_listen (const struct GNUNET_CONFIGURATION_Handle *cfg,
                    const struct GNUNET_HashCode *app_id,
                    GNUNET_SETI_ListenCallback listen_cb,
                    void *listen_cls);


/**
 * Cancel the given listen operation.  After calling cancel, the
 * listen callback for this listen handle will not be called again.
 * Note that cancelling a listen operation will automatically reject
 * all operations that have not yet been accepted.
 *
 * @param lh handle for the listen operation
 */
void
GNUNET_SETI_listen_cancel (struct GNUNET_SETI_ListenHandle *lh);


/**
 * Accept a request we got via GNUNET_SETI_listen().  Must be called during
 * GNUNET_SETI_listen(), as the `struct GNUNET_SETI_Request` becomes invalid
 * afterwards.
 * Call GNUNET_SETI_commit() to provide the local set to use for the operation,
 * and to begin the exchange with the remote peer.
 *
 * @param request request to accept
 * @param options options to use when processing the request
 * @param result_cb callback for the results
 * @param result_cls closure for @a result_cb
 * @return a handle to cancel the operation
 */
struct GNUNET_SETI_OperationHandle *
GNUNET_SETI_accept (struct GNUNET_SETI_Request *request,
                    const struct GNUNET_SETI_Option options[],
                    GNUNET_SETI_ResultIterator result_cb,
                    void *result_cls);


/**
 * Commit a set to be used with a set operation.
 * This function is called once we have fully constructed
 * the set that we want to use for the operation.  At this
 * time, the P2P protocol can then begin to exchange the
 * set information and call the result callback with the
 * result information.
 *
 * @param oh handle to the set operation
 * @param set the set to use for the operation
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if the
 *         set is invalid (e.g. the set service crashed)
 */
int
GNUNET_SETI_commit (struct GNUNET_SETI_OperationHandle *oh,
                    struct GNUNET_SETI_Handle *set);


/**
 * Cancel the given set operation.  May not be called after the operation's
 * `GNUNET_SETI_ResultIterator` has been called with a status of
 * #GNUNET_SETI_STATUS_FAILURE or #GNUNET_SETI_STATUS_DONE.
 *
 * @param oh set operation to cancel
 */
void
GNUNET_SETI_operation_cancel (struct GNUNET_SETI_OperationHandle *oh);


/**
 * Hash a set element.
 *
 * @param element the element that should be hashed
 * @param[out] ret_hash a pointer to where the hash of @a element
 *        should be stored
 */
void
GNUNET_SETI_element_hash (const struct GNUNET_SETI_Element *element,
                          struct GNUNET_HashCode *ret_hash);


#if 0                           /* keep Emacsens' auto-indent happy */
{
#endif
#ifdef __cplusplus
}
#endif

#endif

/** @} */  /* end of group */
