// Baidu RPC - A framework to host and access services throughout Baidu.
// Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved

// Author: The baidu-rpc authors (pbrpc@baidu.com)
// Date: Sun Aug 31 16:27:49 CST 2014

#ifndef BRPC_CHANNEL_H
#define BRPC_CHANNEL_H

// To baidu-rpc developers: This is a header included by user, don't depend
// on internal structures, use opaque pointers instead.

#include <ostream>                               // std::ostream
#include "bthread/errno.h"                       // Redefine errno
#include "base/intrusive_ptr.hpp"               // base::intrusive_ptr
#include "brpc/channel_base.h"              // ChannelBase
#include "brpc/adaptive_protocol_type.h"    // AdaptiveProtocolType
#include "brpc/adaptive_connection_type.h"  // AdaptiveConnectionType
#include "brpc/socket_id.h"                 // SocketId
#include "brpc/controller.h"                // brpc::Controller
#include "brpc/details/profiler_linker.h"
#include "brpc/retry_policy.h"
#include "brpc/naming_service_filter.h"

namespace brpc {

struct ChannelOptions {
    // Constructed with default options.
    ChannelOptions();

    // Issue error when a connection is not established after so many
    // milliseconds. -1 means wait indefinitely.
    // Default: 200 (milliseconds)
    // Maximum: 0x7fffffff (roughly 30 days)
    int32_t connect_timeout_ms;
    
    // Max duration of RPC over this Channel. -1 means wait indefinitely.
    // Overridable by Controller.set_timeout_ms().
    // Default: 500 (milliseconds)
    // Maximum: 0x7fffffff (roughly 30 days)
    int32_t timeout_ms;

    // Send another request if RPC does not finish after so many milliseconds.
    // Overridable by Controller.set_backup_request_ms().
    // The request will be sent to a different server by best effort.
    // If timeout_ms is set and backup_request_ms >= timeout_ms, backup request
    // will never be sent.
    // backup request does NOT imply server-side cancelation.
    // Default: -1 (disabled)
    // Maximum: 0x7fffffff (roughly 30 days)
    int32_t backup_request_ms;

    // Retry limit for RPC over this Channel. <=0 means no retry.
    // Overridable by Controller.set_max_retry().
    // Default: 3
    // Maximum: INT_MAX
    int max_retry;
    
    // Serialization protocol, defined in protocol/brpc/options.proto
    // NOTE: You can assign name of the protocol to this field as well, for
    // Example: options.protocol = "baidu_std";
    AdaptiveProtocolType protocol;

    // Type of connection to server. If unset, use the default connection type
    // of the protocol.
    // NOTE: You can assign name of the type to this field as well, for
    // Example: options.connection_type = "single";
    // Possible values: "single", "pooled", "short".
    AdaptiveConnectionType connection_type;

    // Channel.Init() succeeds even if there's no server in the NamingService. 
    // E.g. the BNS directory is empty. All RPC over the channel will fail before
    // new nodes being added to the NamingService.
    // Default: true (false before r32470)
    bool succeed_without_server;
    // Print a log when above situation happens.
    // Default: true.
    bool log_succeed_without_server;
    
    // Turn on authentication for this channel if `auth' is not NULL.
    // Note `auth' will not be deleted by channel and must remain valid when
    // the channel is being used.
    // Default: NULL
    const Authenticator* auth;

    // Customize the error code that should be retried. The interface is
    // defined src/brpc/retry_policy.h
    // This object is NOT owned by channel and should remain valid when
    // channel is used.
    const RetryPolicy* retry_policy;

    // Filter ServerNodes (i.e. based on `tag' field of `ServerNode')
    // which are generated by NamingService. The interface is defined
    // in src/brpc/naming_service_filter.h
    // This object is NOT owned by channel and should remain valid when
    // channel is used.
    const NamingServiceFilter* ns_filter;
};

// A Channel represents a communication line to a server which can be used
// to call that Server's services. The server may be running on another
// machine. Normally, you should not call a Channel directly, but instead
// construct a stub Service wrapping it.
// Example:
//   brpc::Channel channel;
//   channel.Init("bns://rdev.matrix.all", "rr", NULL/*default options*/);
//   MyService_Stub stub(&channel);
//   stub.MyMethod(&controller, &request, &response, NULL);
class Channel : public ChannelBase {
friend class Controller;
friend class SelectiveChannel;
public:
    Channel(ProfilerLinker = ProfilerLinker());
    ~Channel();

    // Connect this channel to a single server whose address is given by the
    // first parameter. Use default options if `options' is NULL.
    int Init(base::EndPoint server_addr_and_port, const ChannelOptions* options);
    int Init(const char* server_addr_and_port, const ChannelOptions* options);
    int Init(const char* server_addr, int port, const ChannelOptions* options);

    // Connect this channel to a group of servers whose addresses can be
    // accessed via `naming_service_url' according to its protocol. Use the
    // method specified by `load_balancer_name' to distribute traffic to 
    // servers. Use default options if `options' is NULL.
    // Supported naming service("protocol://service_name"):
    //   bns://<node-name>            # Baidu Naming Service
    //   file://<file-path>           # load addresses from the file
    //   list://addr1,addr2,...       # use the addresses separated by comma
    //   http://<url>                 # Domain Naming Service, aka DNS.
    // Supported load balancer:
    //   rr                           # round robin, choose next server
    //   random                       # randomly choose a server
    //   la                           # locality aware
    //   c_murmurhash/c_md5           # consistent hashing with murmurhash3/md5
    //   "" or NULL                   # treat `naming_service_url' as `server_addr_and_port'
    //                                # Init(xxx, "", options) and Init(xxx, NULL, options)
    //                                # are exactly same with Init(xxx, options)
    int Init(const char* naming_service_url, 
             const char* load_balancer_name,
             const ChannelOptions* options);

    // Call `method' of the remote service with `request' as input, and 
    // `response' as output. `controller' contains options and extra data.
    // If `done' is not NULL, this method returns after request was sent
    // and `done->Run()' will be called when the call finishes, otherwise
    // caller blocks until the call finishes.
    void CallMethod(const google::protobuf::MethodDescriptor* method,
                    google::protobuf::RpcController* controller,
                    const google::protobuf::Message* request,
                    google::protobuf::Message* response,
                    google::protobuf::Closure* done);

    // Get current options.
    const ChannelOptions& options() const { return _options; }

    void Describe(std::ostream&, const DescribeOptions&) const;

    // Sum of weights of servers that this channel connects to.
    int Weight();

protected:
    int CheckHealth();

    bool SingleServer() const { return _lb.get() == NULL; }

    // Pick a server using `lb' and then send RPC. Wait for response when 
    // sending synchronous RPC.
    // NOTE: DO NOT directly use `controller' after this call when
    // sending asynchronous RPC (controller->_done != NULL) since
    // user callback `done' could be called when it returns and
    // therefore destroy the `controller' inside `done'
    static void CallMethodImpl(Controller* controller, SharedLoadBalancer* lb);

    int InitChannelOptions(const ChannelOptions* options);

    base::EndPoint _server_address;
    SocketId _server_id;
    Protocol::SerializeRequest _serialize_request;
    Protocol::PackRequest _pack_request;
    Protocol::GetMethodName _get_method_name;
    // This will be shared between channel and controllers that
    // are in the middle of RPC procedure using this channel.
    // It will be destroyed after channel's destruction and all
    // the RPC above has finished
    base::intrusive_ptr<SharedLoadBalancer> _lb;
    ChannelOptions _options;
    int _preferred_index;
};

enum ChannelOwnership {
    OWNS_CHANNEL,
    DOESNT_OWN_CHANNEL
};

} // namespace brpc


#endif  // BRPC_CHANNEL_H
