syntax = "proto2";
package command;

// A message received by Sōzu to change its state or query information
message Request {
  oneof request_type {
    // This message tells Sōzu to dump the current proxy state (backends,
    // front domains, certificates, etc) as a list of JSON-serialized Requests,
    // separated by a 0 byte, to a file. This file can be used later
    // to bootstrap the proxy. This message is not forwarded to workers.
    // If the specified path is relative, it will be calculated relative to the current
    // working directory of the proxy.
    string save_state = 1;
    // load a state file, given its path
    string load_state = 2;
    // list the workers and their status
    ListWorkers list_workers = 4;
    // list the frontends, filtered by protocol and/or domain
    FrontendFilters list_frontends = 5;
    // list all listeners
    ListListeners list_listeners = 6;
    // launch a new worker
    // never implemented, the tag is unused and probably not needed
    // we may still implement it later with no paramater
    // the main process will automatically assign a new id to a new worker
    string launch_worker = 7;
    // upgrade the main process
    UpgradeMain upgrade_main = 8;
    // upgrade an existing worker, giving its id
    uint32 upgrade_worker = 9;
    // subscribe to proxy events
    SubscribeEvents subscribe_events = 10;
    // reload the configuration from the config file, or a new file
    // CHECK: this used to be an option. None => use the config file, Some(string) => path_to_file
    // make sure it works using "" and "path_to_file"
    string reload_configuration = 11;
    // give status of main process and all workers
    Status status = 12;
    // add a cluster
    Cluster add_cluster = 13;
    // remove a cluster giving its id
    string remove_cluster = 14;
    // add an HTTP frontend
    RequestHttpFrontend add_http_frontend = 15;
    // remove an HTTP frontend
    RequestHttpFrontend remove_http_frontend = 16;
    // add an HTTPS frontend
    RequestHttpFrontend add_https_frontend = 17;
    // remove an HTTPS frontend
    RequestHttpFrontend remove_https_frontend = 18;
    // add a certificate
    AddCertificate add_certificate = 19;
    // replace a certificate
    ReplaceCertificate replace_certificate = 20;
    // remove a certificate
    RemoveCertificate remove_certificate = 21;
    // add a TCP frontend
    RequestTcpFrontend add_tcp_frontend = 22;
    // remove a TCP frontend
    RequestTcpFrontend remove_tcp_frontend = 23;
    // add a backend
    AddBackend add_backend = 24;
    // remove a backend
    RemoveBackend remove_backend = 25;
    // add an HTTP listener
    HttpListenerConfig add_http_listener = 26;
    // add an HTTPS listener
    HttpsListenerConfig add_https_listener = 27;
    // add a TCP listener
    TcpListenerConfig add_tcp_listener = 28;
    // remove a listener
    RemoveListener remove_listener = 29;
    // activate a listener
    ActivateListener activate_listener = 30;
    // deactivate a listener
    DeactivateListener deactivate_listener = 31;
    // query a cluster by id
    string query_cluster_by_id = 35;
    // query clusters with a hostname and optional path
    QueryClusterByDomain query_clusters_by_domain = 36;
    // query clusters hashes
    QueryClustersHashes query_clusters_hashes = 37;
    // query metrics
    QueryMetricsOptions query_metrics = 38;
    // soft stop
    SoftStop soft_stop = 39;
    // hard stop
    HardStop hard_stop = 40;
    // enable, disable or clear the metrics 
    MetricsConfiguration configure_metrics = 41;
    // Change the logging level
    string Logging = 42;
    // Return the listen sockets
    ReturnListenSockets return_listen_sockets = 43;
    // Get certificates from the state (rather than from the workers)
    QueryCertificatesFilters query_certificates_from_the_state = 44;
    // Get certificates from the workers (rather than from the state)
    QueryCertificatesFilters query_certificates_from_workers = 45;
    // query the state about how many requests of each type has been received
    // since startup
    CountRequests count_requests = 46;
  }
}

message ListWorkers {}
message ListListeners {}
message UpgradeMain {}
message SubscribeEvents {}
message Status {}
message QueryClustersHashes {}
message SoftStop {}
message HardStop {}
message ReturnListenSockets {}
message CountRequests {}

// details of an HTTP listener
message HttpListenerConfig {
    required SocketAddress address = 1;
    optional SocketAddress public_address = 2;
    required bool expect_proxy = 5 [default = false];
    required string sticky_name = 6;
    // client inactive time, in seconds
    required uint32 front_timeout = 7 [default = 60];
    // backend server inactive time, in seconds
    required uint32 back_timeout = 8 [default = 30];
    // time to connect to the backend, in seconds
    required uint32 connect_timeout = 9 [default = 3];
    // max time to send a complete request, in seconds
    required uint32 request_timeout = 10 [default = 10];
    // wether the listener is actively listening on its socket
    required bool active = 11 [default = false];
    optional CustomHttpAnswers http_answers = 12;
}

// details of an HTTPS listener
message HttpsListenerConfig {
    required SocketAddress address = 1;
    optional SocketAddress public_address = 2;
    required bool expect_proxy = 5 [default = false];
    required string sticky_name = 6;
    // client inactive time, in seconds
    required uint32 front_timeout = 7 [default = 60];
    // backend server inactive time, in seconds
    required uint32 back_timeout = 8 [default = 30];
    // time to connect to the backend, in seconds
    required uint32 connect_timeout = 9 [default = 3];
    // max time to send a complete request, in seconds
    required uint32 request_timeout = 10 [default = 10];
    // wether the listener is actively listening on its socket
    required bool active = 11 [default = false];
    // TLS versions
    repeated TlsVersion versions = 12;
    repeated string cipher_list = 13;
    repeated string cipher_suites = 14;
    repeated string signature_algorithms = 15;
    repeated string groups_list = 16;
    optional string certificate = 17;
    repeated string certificate_chain = 18;
    optional string key = 19;
    // Number of TLS 1.3 tickets to send to a client when establishing a connection.
    // The tickets allow the client to resume a session. This protects the client
    // agains session tracking. Defaults to 4.
    required uint64 send_tls13_tickets = 20;
    optional CustomHttpAnswers http_answers = 21;
}

// details of an TCP listener
message TcpListenerConfig {
    required SocketAddress address = 1;
    optional SocketAddress public_address = 2;
    required bool expect_proxy = 3 [default = false];
    // client inactive time, in seconds
    required uint32 front_timeout = 4 [default = 60];
    // backend server inactive time, in seconds
    required uint32 back_timeout = 5 [default = 30];
    // time to connect to the backend, in seconds
    required uint32 connect_timeout = 6 [default = 3];
    // wether the listener is actively listening on its socket
    required bool active = 7 [default = false];
}

// custom HTTP answers, useful for 404, 503 pages
message CustomHttpAnswers {
    // MovedPermanently
    optional string answer_301 = 1;
    // BadRequest
    optional string answer_400 = 2;
    // Unauthorized
    optional string answer_401 = 3;
    // NotFound
    optional string answer_404 = 4;
    // RequestTimeout
    optional string answer_408 = 5;
    // PayloadTooLarge
    optional string answer_413 = 6;
    // BadGateway
    optional string answer_502 = 7;
    // ServiceUnavailable
    optional string answer_503 = 8;
    // GatewayTimeout
    optional string answer_504 = 9;
    // InsufficientStorage
    optional string answer_507 = 10;

}

message ActivateListener {
    required SocketAddress address = 1;
    required ListenerType proxy = 2;
    required bool from_scm = 3;
}

message DeactivateListener {
    required SocketAddress address = 1;
    required ListenerType proxy = 2;
    required bool to_scm = 3;
}

message RemoveListener {
    required SocketAddress address = 1;
    required ListenerType proxy = 2;
}

enum ListenerType {
    HTTP = 0;
    HTTPS = 1;
    TCP = 2;
}

// All listeners, listed
message ListenersList {
    // address -> http listener config
    map<string, HttpListenerConfig> http_listeners = 1;
    // address -> https listener config
    map<string, HttpsListenerConfig> https_listeners = 2;
    // address -> tcp listener config
    map<string, TcpListenerConfig> tcp_listeners = 3;
}

// An HTTP or HTTPS frontend, as order to, or received from, Sōzu
message RequestHttpFrontend {
    optional string cluster_id = 1;
    required SocketAddress address = 2;
    required string hostname = 3;
    required PathRule path = 4;
    optional string method = 5;
    required RulePosition position = 6 [default = TREE];
    // custom tags to identify the frontend in the access logs
    map<string, string> tags = 7;
}

message RequestTcpFrontend {
    required string cluster_id = 1;
    // the socket address on which to listen for incoming traffic
    required SocketAddress address = 2;
    // custom tags to identify the frontend in the access logs
    map<string, string> tags = 3;
}

// list the frontends, filtered by protocol and/or domain
message FrontendFilters {
    required bool http = 1;
    required bool https = 2;
    required bool tcp = 3;
    optional string domain = 4;
}

// A filter for the path of incoming requests
message PathRule {
    // The kind of filter used for path rules
    required PathRuleKind kind = 1;
    // the value of the given prefix, regex or equal pathrule
    required string value = 2;
}

// The kind of filter used for path rules
enum PathRuleKind {
    // filters paths that start with a pattern, typically "/api"
    PREFIX = 0;
    // filters paths that match a regex pattern
    REGEX = 1;
    // filters paths that exactly match a pattern, no more, no less
    EQUALS = 2;
}

// TODO: find a proper definition for this
enum RulePosition {
    PRE = 0;
    POST = 1;
    TREE = 2;
}

// Add a new TLS certificate to an HTTPs listener
message AddCertificate {
    required SocketAddress address = 1;
    required CertificateAndKey certificate = 2;
    // A unix timestamp. Overrides certificate expiration.
    optional int64 expired_at = 3;
}

message RemoveCertificate {
    required SocketAddress address = 1;
    // a hex-encoded TLS fingerprint to identify the certificate to remove
    required string fingerprint = 2;
}

message ReplaceCertificate {
    required SocketAddress address = 1;
    required CertificateAndKey new_certificate = 2;
    // a hex-encoded TLS fingerprint to identify the old certificate
    required string old_fingerprint = 3;
    // A unix timestamp. Overrides certificate expiration.
    optional int64 new_expired_at = 4;
}

message CertificateAndKey {
    required string certificate = 1;
    repeated string certificate_chain = 2;
    required string key = 3;
    repeated TlsVersion versions = 4;
    // a list of domain names. Override certificate names
    // if empty, the names of the certificate will be used
    repeated string names = 5;
}

// Should be either a domain name or a fingerprint.
// These filter do not compound, use either one but not both.
// If none of them is specified, all certificates will be returned.
message QueryCertificatesFilters {
    // a domain name to filter certificate results
    optional string domain = 1;
    // a hex-encoded fingerprint of the TLS certificate to find
    optional string fingerprint = 2;
}

// domain name and fingerprint of a certificate
message CertificateSummary {
    required string domain = 1;
    // a hex-encoded TLS fingerprint
    required string fingerprint = 2;
}

// Used by workers to reply to some certificate queries
message ListOfCertificatesByAddress {
    repeated CertificatesByAddress certificates = 1;
}

// Summaries of certificates for a given address
message CertificatesByAddress {
    required SocketAddress address = 1;
    repeated CertificateSummary certificate_summaries = 2;
}

// to reply to several certificate queries
message CertificatesWithFingerprints {
    // a map of fingerprint -> certificate_and_key
    map<string, CertificateAndKey> certs = 1;
}

enum TlsVersion {
    SSL_V2 = 0;
    SSL_V3 = 1;
    TLS_V1_0 = 2;
    TLS_V1_1 = 3;
    TLS_V1_2 = 4;
    TLS_V1_3 = 5;
}

// A cluster is what binds a frontend to backends with routing rules
message Cluster {
    required string cluster_id = 1;
    // wether a connection from a client shall be always redirected to the same backend
    required bool sticky_session = 2;
    required bool https_redirect = 3;
    optional ProxyProtocolConfig proxy_protocol = 4;
    required LoadBalancingAlgorithms load_balancing = 5 [default = ROUND_ROBIN];
    optional string answer_503 = 6;
    optional LoadMetric load_metric = 7;
}

enum LoadBalancingAlgorithms {
    ROUND_ROBIN = 0;
    RANDOM = 1;
    LEAST_LOADED = 2;
    POWER_OF_TWO = 3;
}

enum ProxyProtocolConfig {
    EXPECT_HEADER = 0;
    SEND_HEADER = 1;
    RELAY_HEADER = 2;
}

// how sozu measures which backend is less loaded
enum LoadMetric {
    // number of TCP connections
    CONNECTIONS = 0;
    // number of active HTTP requests
    REQUESTS = 1;
    // time to connect to the backend, weighted by the number of active connections (peak EWMA)
    CONNECTION_TIME = 2;
}

// add a backend
message AddBackend {
    required string cluster_id = 1;
    required string backend_id = 2;
    // the socket address of the backend
    required SocketAddress address = 3;
    optional string sticky_id = 4;
    optional LoadBalancingParams load_balancing_parameters = 5;
    optional bool backup = 6;
}

// remove an existing backend
message RemoveBackend {
    required string cluster_id = 1;
    required string backend_id = 2;
    // the socket address of the backend
    required SocketAddress address = 3 ;
}

message LoadBalancingParams {
    required int32 weight = 1;
}

message QueryClusterByDomain {
    required string hostname = 1;
    optional string path = 2;
}

// Options when querying metrics
message QueryMetricsOptions {
    // query a list of available metrics
    required bool list = 1;
    // query metrics for these clusters
    repeated string cluster_ids = 2;
    // query metrics for these backends
    repeated string backend_ids = 3;
    // query only these metrics
    repeated string metric_names = 4;
    // query only worker and main process metrics (no cluster metrics)
    required bool no_clusters = 5;
    // display metrics of each worker, without flattening (takes more space)
    required bool workers = 6;
}

// options to configure metrics collection
enum MetricsConfiguration {
    // enable metrics collection
    ENABLED = 0;
    // disable metrics collection
    DISABLED = 1;
    // wipe the metrics memory
    CLEAR = 2;
}

// Response to a request
message Response {
    // wether the request was a success, a failure, or is processing
    required ResponseStatus status = 1 [default = FAILURE];
    // a success or error message
    required string message = 2;
    // response data, if any
    optional ResponseContent content = 3;
}


// content of a response
message ResponseContent {
    oneof content_type {
        // a list of workers, with ids, pids, statuses
        WorkerInfos workers = 1;
        // aggregated metrics of main process and workers
        AggregatedMetrics metrics = 2;
        // a collection of worker responses to the same request
        WorkerResponses worker_responses = 3;
        // a proxy event
        Event event = 4;
        // a filtered list of frontend
        ListedFrontends frontend_list = 5;
        // all listeners
        ListenersList listeners_list = 6;
        // contains proxy & cluster metrics
        WorkerMetrics worker_metrics = 7;
        // Lists of metrics that are available
        AvailableMetrics available_metrics = 8;
        // a list of cluster informations
        ClusterInformations clusters = 9;
        // collection of hashes of cluster information, 
        ClusterHashes cluster_hashes = 10;
        // a list of certificates summaries, grouped by socket address
        ListOfCertificatesByAddress certificates_by_address = 11;
        // a map of complete certificates using fingerprints as key
        CertificatesWithFingerprints certificates_with_fingerprints = 12;
        // a census of the types of requests received since startup,
        RequestCounts request_counts = 13;
    }
}

// a map of worker_id -> ResponseContent
message WorkerResponses {
    map<string, ResponseContent> map = 1;
}

// lists of frontends present in the state
message ListedFrontends {
    repeated RequestHttpFrontend http_frontends = 1;
    repeated RequestHttpFrontend https_frontends = 2;
    repeated RequestTcpFrontend tcp_frontends = 3;
}

message ClusterInformations {
    repeated ClusterInformation vec = 1;
}

// Information about a given cluster
// Contains types usually used in requests, because they are readily available in protobuf
message ClusterInformation {
    optional Cluster configuration = 1;
    repeated RequestHttpFrontend http_frontends = 2;
    repeated RequestHttpFrontend https_frontends = 3;
    repeated RequestTcpFrontend tcp_frontends = 4;
    repeated AddBackend backends = 5;
}

// an event produced by a worker to notify about backends status
message Event {
    required EventKind kind = 1;
    optional string cluster_id = 2;
    optional string backend_id = 3;
    optional SocketAddress address = 4;
}

enum EventKind {
    BACKEND_DOWN = 0;
    BACKEND_UP = 1;
    NO_AVAILABLE_BACKENDS = 2;
    REMOVED_BACKEND_HAS_NO_CONNECTIONS = 3;
}

message ClusterHashes {
    // cluster id -> hash of cluster information
    map<string, uint64> map = 1;
}

enum ResponseStatus {
    OK = 0;
    PROCESSING = 1;
    FAILURE = 2;
}

// A list of worker infos
message WorkerInfos {
    repeated WorkerInfo vec = 1;
}

// Information about a worker with id, pid, runstate
message WorkerInfo {
    required uint32 id = 1;
    required int32 pid = 2;
    required RunState run_state = 3;
}

// Runstate of a worker
enum RunState {
    RUNNING = 0;
    STOPPING = 1;
    STOPPED = 2;
    NOT_ANSWERING = 3;
}

// lists of available metrics in a worker, or in the main process (in which case there are no cluster metrics)
message AvailableMetrics {
    repeated string proxy_metrics = 1;
    repeated string cluster_metrics = 2;
}

// Aggregated metrics of main process & workers
message AggregatedMetrics {
    // metrics about the main process.
    // metric_name -> metric_value
    map<string, FilteredMetrics> main = 1;
    // details of worker metrics, with clusters and backends.
    // worker_id -> worker_metrics
    map<string, WorkerMetrics> workers = 2;
    // if present, contains metrics of clusters and their backends, merged across all workers.
    // cluster_id -> cluster_metrics
    map<string, ClusterMetrics> clusters = 3;
    // if present, proxying metrics, merged accross all workers.
    // metric_name -> metric_value
    map<string, FilteredMetrics> proxying = 4;
}

// All metrics of a worker: proxy and clusters
// Populated by Options so partial results can be sent
message WorkerMetrics {
   // Metrics of the worker process, key -> value
    map<string, FilteredMetrics> proxy = 1;
    // cluster_id -> cluster_metrics
    map<string, ClusterMetrics> clusters = 2;
}

// the metrics of a given cluster, with several backends
message ClusterMetrics {
    // metric name -> metric value
    map<string, FilteredMetrics> cluster = 1;
    // list of backends with their metrics
    repeated BackendMetrics backends = 2;
}

message BackendMetrics {
    required string backend_id = 1;
    map<string, FilteredMetrics> metrics = 2;
}

// A metric, in a "filtered" format, which means: sendable to outside programs.
message FilteredMetrics {
    oneof inner {
        // increases or decrease depending on the state
        uint64 gauge = 1;
        // increases only
        int64 count = 2;
        // milliseconds
        uint64 time = 3;
        Percentiles percentiles = 4;
        FilteredTimeSerie time_serie = 5;
        FilteredHistogram histogram = 6;
    }
}

message FilteredTimeSerie {
    required uint32 last_second = 1;
    repeated uint32 last_minute = 2;
    repeated uint32 last_hour = 3;
}

message Percentiles {
    required uint64 samples = 1;
    required uint64 p_50 = 2;
    required uint64 p_90 = 3;
    required uint64 p_99 = 4;
    required uint64 p_99_9 = 5;
    required uint64 p_99_99 = 6;
    required uint64 p_99_999 = 7;
    required uint64 p_100 = 8;
    required uint64 sum = 9;
}

// a histogram meant to be translated to prometheus
message FilteredHistogram {
    required uint64 sum = 1;
    required uint64 count = 2;
    repeated Bucket buckets = 3;
}

// a prometheus histogram bucket
message Bucket {
    required uint64 count = 1;
    // upper range of the bucket (le = less or equal)
    required uint64 le = 2;
}

message RequestCounts {
    map<string, int32> map = 1;
}

// matches std::net::SocketAddr in the Rust library
// beware that the ports are expressed with uint32 here,
// but they should NOT exceed uint16 value
message SocketAddress {
    required IpAddress ip = 1;
    required uint32 port = 2;
}

message IpAddress {
    oneof inner {
        fixed32 v4 = 1;
        Uint128 v6 = 2;
    }
}

// used to represent the 128 bits of an IPv6 address
message Uint128 {
    // higher value, first 8 bytes of the ip
    required uint64 low = 1;
    // lower value, last 8 bytes of the ip
    required uint64 high = 2;
}

// This is sent only from Sōzu to Sōzu
message WorkerRequest {
    required string id = 1;
    required Request content = 2;
}

// A response as sent by a worker
message WorkerResponse {
    required string id = 1;
    required ResponseStatus status = 2;
    // an associated message to detail failure, success or processing
    required string message = 3;
    optional ResponseContent content = 4;
}

// intended to workers
message ServerMetricsConfig {
    required string address = 1;
    required bool tagged_metrics = 2;
    optional string prefix = 3;
}

// Used by a worker to start its server loop.
// The defaults should match those of the config module
message ServerConfig {
    required uint64 max_connections = 1 [default = 10000];
    required uint32 front_timeout = 2 [default = 60];
    required uint32 back_timeout = 3 [default = 30];
    required uint32 connect_timeout = 4 [default = 3];
    required uint32 zombie_check_interval = 5 [default = 1800];
    required uint32 accept_queue_timeout = 6 [default = 60];
    required uint64 min_buffers = 7 [default = 1];
    required uint64 max_buffers = 8 [default = 1000];
    required uint64 buffer_size = 9 [default = 16393];
    required string log_level = 10 [default = "info"];
    required string log_target = 11 [default = "stdout"];
    optional string access_logs_target = 12;
    required uint64 command_buffer_size = 13 [default = 1000000];
    required uint64 max_command_buffer_size = 14 [default = 2000000];
    optional ServerMetricsConfig metrics = 15;
    required ProtobufAccessLogFormat access_log_format = 16;
    required bool log_colored = 17;
}

enum ProtobufAccessLogFormat {
    Ascii = 1;
    Protobuf = 2;
}

// Addresses of listeners, passed to new workers
message ListenersCount {
    // socket addresses of HTTP listeners
    repeated string http = 1;
    // socket addresses of HTTPS listeners
    repeated string tls = 2;
    // socket addresses of TCP listeners
    repeated string tcp = 3;
}

// the Sōzu state, passed to a new worker.
// Consists in a collection of worker requests
message InitialState {
    repeated WorkerRequest requests = 1;
}

message OpenTelemetry {
    required string trace_id = 1;
    required string span_id = 2;
    optional string parent_span_id = 3;
}

// An access log, meant to be passed to another agent
message ProtobufAccessLog {
    // error message if any
    optional string message = 1;
    // LogContext = request_id + cluster_id + backend_id
    required Uint128 request_id = 2;
    // id of the cluster (set of frontend, backend, routing rules)
    optional string cluster_id = 3;
    // id of the backend (the server to which the traffic is redirected)
    optional string backend_id = 4;
    // ip and port of the client
    optional SocketAddress session_address = 5;
    // socket address of the backend server
    optional SocketAddress backend_address = 6;
    // the protocol, with SSL/TLS version, for instance "HTTPS-TLS1.1"
    required string protocol = 7;
    // TCP or HTTP endpoint (method, path, context...)
    required ProtobufEndpoint endpoint = 8;
    // round trip time for the client (microseconds)
    optional uint64 client_rtt = 9;
    // round trip time for the backend (microseconds)
    optional uint64 server_rtt = 10;
    // time spent on a session (microseconds)
    required uint64 service_time = 13;
    // number of bytes received from the client
    required uint64 bytes_in = 14;
    // number of bytes written to the client
    required uint64 bytes_out = 15;
    // value of the User-Agent header, if any
    optional string user_agent = 16;
    // custom tags as key-values, for instance owner_id: MyOrganisation
    map<string, string> tags = 17;
    // short description of which process sends the log, for instance: "WRK-02"
    required string tag = 18;
    // POSIX timestamp, nanoseconds
    required Uint128 time = 19;
    // Entire time between first byte received and last byte of the response.
    // If a request ends abruptly before the last byte is transmitted,
    // the `request_time` produced is the time elapsed since the first byte received.
    optional uint64 request_time = 20;
    // time for the backend to respond (microseconds)
    optional uint64 response_time = 21;
    // OpenTelemetry tracing information
    optional OpenTelemetry otel = 22;
}

message ProtobufEndpoint {
    oneof inner {
        HttpEndpoint http = 1;
        TcpEndpoint tcp = 2;
    }
}

message HttpEndpoint {
    optional string method = 1;
    optional string authority = 2;
    optional string path = 3;
    // warning: this should be a u16 but protobuf only has uint32.
    // Make sure the value never exceeds u16 bounds.
    optional uint32 status = 4;
    optional string reason = 5;
}

message TcpEndpoint {}
