// Copyright 2016 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package trillian;

option go_package = "github.com/google/trillian";
option java_multiple_files = true;
option java_outer_classname = "TrillianLogApiProto";
option java_package = "com.google.trillian.proto";

import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "google/rpc/status.proto";
import "trillian.proto";

// The TrillianLog service provides access to an append-only Log data structure
// as described in the [Verifiable Data
// Structures](docs/papers/VerifiableDataStructures.pdf) paper.
//
// The API supports adding new entries to the Merkle tree for a specific Log
// instance (identified by its log_id) in two modes:
//  - For a normal log, new leaf entries are queued up for subsequent
//    inclusion in the log, and the leaves are assigned consecutive leaf_index
//    values as part of that integration process.
//  - For a 'pre-ordered log', new entries have an already-defined leaf
//    ordering, and leaves are only integrated into the Merkle tree when a
//    contiguous range of leaves is available.
//
// The API also supports read operations to retrieve leaf contents, and to
// provide cryptographic proofs of leaf inclusion and of the append-only nature
// of the Log.
//
// Each API request also includes a charge_to field, which allows API users
// to provide quota identifiers that should be "charged" for each API request
// (and potentially rejected with codes.ResourceExhausted).
//
// Various operations on the API also allows for 'server skew', which can occur
// when different API requests happen to be handled by different server instances
// that may not all be up to date.  An API request that is relative to a specific
// tree size may reach a server instance that is not yet aware of this tree size;
// in this case the server will typically return an OK response that contains:
//  - a signed log root that indicates the tree size that it is aware of
//  - an empty response otherwise.
service TrillianLog {
  // QueueLeaf adds a single leaf to the queue of pending leaves for a normal
  // log.
  rpc QueueLeaf(QueueLeafRequest) returns (QueueLeafResponse) {
    option (google.api.http) = {
      post: "/v1beta1/logs/{log_id}/leaves"
      body: "*"
    };
  }

  // AddSequencedLeaf adds a single leaf with an assigned sequence number to a
  // pre-ordered log.
  rpc AddSequencedLeaf(AddSequencedLeafRequest)
      returns (AddSequencedLeafResponse) {
    option (google.api.http) = {
      post: "/v1beta1/logs/{log_id}/leaves:sequenced"
      body: "*"
    };
  }


  // GetInclusionProof returns an inclusion proof for a leaf with a given index
  // in a particular tree.
  //
  // If the requested tree_size is larger than the server is aware of, the
  // response will include the latest known log root and an empty proof.
  rpc GetInclusionProof(GetInclusionProofRequest)
      returns (GetInclusionProofResponse) {
    option (google.api.http) = {
      get: "/v1beta1/logs/{log_id}/leaves/{leaf_index}:inclusion_proof"
    };
  }

  // GetInclusionProofByHash returns an inclusion proof for any leaves that have
  // the given Merkle hash in a particular tree.
  //
  // If any of the leaves that match the given Merkle has have a leaf index that
  // is beyond the requested tree size, the corresponding proof entry will be empty.
  rpc GetInclusionProofByHash(GetInclusionProofByHashRequest)
      returns (GetInclusionProofByHashResponse) {
    option (google.api.http) = {
      get: "/v1beta1/logs/{log_id}/leaves:inclusion_by_hash"
    };
  }

  // GetConsistencyProof returns a consistency proof between different sizes of
  // a particular tree.
  //
  // If the requested tree size is larger than the server is aware of,
  // the response will include the latest known log root and an empty proof.
  rpc GetConsistencyProof(GetConsistencyProofRequest)
      returns (GetConsistencyProofResponse) {
    option (google.api.http) = {
      get: "/v1beta1/logs/{log_id}:consistency_proof"
    };
  }

  // GetLatestSignedLogRoot returns the latest signed log root for a given tree,
  // and optionally also includes a consistency proof from an earlier tree size
  // to the new size of the tree.
  //
  // If the earlier tree size is larger than the server is aware of,
  // an InvalidArgument error is returned.
  rpc GetLatestSignedLogRoot(GetLatestSignedLogRootRequest)
      returns (GetLatestSignedLogRootResponse) {
    option (google.api.http) = {
      get: "/v1beta1/logs/{log_id}/roots:latest"
    };
  }

  // GetSequencedLeafCount returns the total number of leaves that have been
  // integrated into the given tree.
  //
  // DO NOT USE - FOR DEBUGGING/TEST ONLY
  //
  // (Use GetLatestSignedLogRoot then de-serialize the Log Root and use
  // use the tree size field within.)
  rpc GetSequencedLeafCount(GetSequencedLeafCountRequest)
      returns (GetSequencedLeafCountResponse) {
    option (google.api.http) = {
      get: "/v1beta1/logs/{log_id}/leaves:sequenced_count"
    };
  }

  // GetEntryAndProof returns a log leaf and the corresponding inclusion proof
  // to a specified tree size, for a given leaf index in a particular tree.
  //
  // If the requested tree size is unavailable but the leaf is
  // in scope for the current tree, the returned proof will be for the
  // current tree size rather than the requested tree size.
  rpc GetEntryAndProof(GetEntryAndProofRequest)
      returns (GetEntryAndProofResponse) {
    option (google.api.http) = {
      get: "/v1beta1/logs/{log_id}/leaves/{leaf_index}"
    };
  }

  // InitLog initializes a particular tree, creating the initial signed log
  // root (which will be of size 0).
  rpc InitLog(InitLogRequest) returns (InitLogResponse) {
    option (google.api.http) = {
      post: "/v1beta1/logs/{log_id}:init"
    };
  }


  // QueueLeaf adds a batch of leaves to the queue of pending leaves for a
  // normal log.
  rpc QueueLeaves(QueueLeavesRequest) returns (QueueLeavesResponse) {}

  // AddSequencedLeaves adds a batch of leaves with assigned sequence numbers
  // to a pre-ordered log.  The indices of the provided leaves must be contiguous.
  rpc AddSequencedLeaves(AddSequencedLeavesRequest)
      returns (AddSequencedLeavesResponse) {}

  // GetLeavesByIndex returns a batch of leaves whose leaf indices are provided
  // in the request.
  rpc GetLeavesByIndex(GetLeavesByIndexRequest)
      returns (GetLeavesByIndexResponse) {}

  // GetLeavesByRange returns a batch of leaves whose leaf indices are in a
  // sequential range.
  rpc GetLeavesByRange(GetLeavesByRangeRequest)
      returns (GetLeavesByRangeResponse) {}

  // GetLeavesByHash returns a batch of leaves which are identified by their
  // Merkle leaf hash values.
  rpc GetLeavesByHash(GetLeavesByHashRequest)
      returns (GetLeavesByHashResponse) {}
}

// ChargeTo describes the user(s) associated with the request whose quota should
// be checked and charged.
message ChargeTo {
  // user is a list of personality-defined strings.
  // Trillian will treat them as /User/%{user}/... keys when checking and
  // charging quota.
  // If one or more of the specified users has insufficient quota, the
  // request will be denied.
  //
  // As an example, a Certificate Transparency frontend might set the following
  // user strings when sending a QueueLeaves request to the Trillian log:
  //   - The requesting IP address.
  //     This would limit the number of requests per IP.
  //   - The "intermediate-<hash>" for each of the intermediate certificates in
  //     the submitted chain.
  //     This would have the effect of limiting the rate of submissions under
  //     a given intermediate/root.
  repeated string user = 1;
}

message QueueLeafRequest {
  int64 log_id = 1;
  LogLeaf leaf = 2;
  ChargeTo charge_to = 3;
}

message QueueLeafResponse {
  // queued_leaf describes the leaf which is or will be incorporated into the
  // Log.  If the submitted leaf was already present in the Log (as indicated by
  // its leaf identity hash), then the returned leaf will be the pre-existing
  // leaf entry rather than the submitted leaf.
  QueuedLogLeaf queued_leaf = 2;
}

message AddSequencedLeafRequest {
  int64 log_id = 1;
  LogLeaf leaf = 2;
  ChargeTo charge_to = 3;
}

message AddSequencedLeafResponse {
  QueuedLogLeaf result = 2;
}

message GetInclusionProofRequest {
  int64 log_id = 1;
  int64 leaf_index = 2;
  int64 tree_size = 3;
  ChargeTo charge_to = 4;
}

message GetInclusionProofResponse {
  // The proof field may be empty if the requested tree_size was larger
  // than that available at the server (e.g. because there is skew between
  // server instances, and an earlier client request was processed by a
  // more up-to-date instance).  In this case, the signed_log_root
  // field will indicate the tree size that the server is aware of, and
  // the proof field will be empty.
  Proof proof = 2;
  SignedLogRoot signed_log_root = 3;
}

message GetInclusionProofByHashRequest {
  int64 log_id = 1;
  // The leaf hash field provides the Merkle tree hash of the leaf entry
  // to be retrieved.
  bytes leaf_hash = 2;
  int64 tree_size = 3;
  bool order_by_sequence = 4;
  ChargeTo charge_to = 5;
}

message GetInclusionProofByHashResponse {
  // Logs can potentially contain leaves with duplicate hashes so it's possible
  // for this to return multiple proofs.  If the leaf index for a particular
  // instance of the requested Merkle leaf hash is beyond the requested tree
  // size, the corresponding proof entry will be missing.
  repeated Proof proof = 2;
  SignedLogRoot signed_log_root = 3;
}

message GetConsistencyProofRequest {
  int64 log_id = 1;
  int64 first_tree_size = 2;
  int64 second_tree_size = 3;
  ChargeTo charge_to = 4;
}

message GetConsistencyProofResponse {
  // The proof field may be empty if the requested tree_size was larger
  // than that available at the server (e.g. because there is skew between
  // server instances, and an earlier client request was processed by a
  // more up-to-date instance).  In this case, the signed_log_root
  // field will indicate the tree size that the server is aware of, and
  // the proof field will be empty.
  Proof proof = 2;
  SignedLogRoot signed_log_root = 3;
}

message GetLatestSignedLogRootRequest {
  int64 log_id = 1;
  ChargeTo charge_to = 2;
  // If first_tree_size is non-zero, the response will include a consistency
  // proof between first_tree_size and the new tree size (if not smaller).
  int64 first_tree_size = 3;
}

message GetLatestSignedLogRootResponse {
  SignedLogRoot signed_log_root = 2;
  // proof is filled in with a consistency proof if first_tree_size in
  // GetLatestSignedLogRootRequest is non-zero (and within the tree size
  // available at the server).
  Proof proof = 3;
}

// DO NOT USE - FOR DEBUGGING/TEST ONLY
//
// (Use GetLatestSignedLogRoot then de-serialize the Log Root and use
// use the tree size field within.)
message GetSequencedLeafCountRequest {
  int64 log_id = 1;
  ChargeTo charge_to = 2;
}

message GetSequencedLeafCountResponse {
  int64 leaf_count = 2;
}

message GetEntryAndProofRequest {
  int64 log_id = 1;
  int64 leaf_index = 2;
  int64 tree_size = 3;
  ChargeTo charge_to = 4;
}

message GetEntryAndProofResponse {
  Proof proof = 2;
  LogLeaf leaf = 3;
  SignedLogRoot signed_log_root = 4;
}

message InitLogRequest {
  int64 log_id = 1;
  ChargeTo charge_to = 2;
}

message InitLogResponse {
  SignedLogRoot created = 1;
}

message QueueLeavesRequest {
  int64 log_id = 1;
  repeated LogLeaf leaves = 2;
  ChargeTo charge_to = 3;
}

message QueueLeavesResponse {
  // Same number and order as in the corresponding request.
  repeated QueuedLogLeaf queued_leaves = 2;
}

message AddSequencedLeavesRequest {
  int64 log_id = 1;
  repeated LogLeaf leaves = 2;
  ChargeTo charge_to = 4;
}

message AddSequencedLeavesResponse {
  // Same number and order as in the corresponding request.
  repeated QueuedLogLeaf results = 2;
}

message GetLeavesByIndexRequest {
  int64 log_id = 1;
  repeated int64 leaf_index = 2;
  ChargeTo charge_to = 5;
}

message GetLeavesByIndexResponse {
  // TODO(gbelvin): Response syntax does not allow for some requested leaves to be available, and some not (but using QueuedLogLeaf might)
  repeated LogLeaf leaves = 2;
  SignedLogRoot signed_log_root = 3;
}

message GetLeavesByRangeRequest {
  int64 log_id = 1;
  int64 start_index = 2;
  int64 count = 3;
  ChargeTo charge_to = 4;
}

message GetLeavesByRangeResponse {
  // Returned log leaves starting from the `start_index` of the request, in
  // order. There may be fewer than `request.count` leaves returned, if the
  // requested range extended beyond the size of the tree or if the server opted
  // to return fewer leaves than requested.
  repeated LogLeaf leaves = 1;
  SignedLogRoot signed_log_root = 2;
}

message GetLeavesByHashRequest {
  int64 log_id = 1;
  // The Merkle leaf hash of the leaf to be retrieved.
  repeated bytes leaf_hash = 2;
  // If order_by_sequence is set then leaves will be returned in order of ascending
  // leaf index.
  bool order_by_sequence = 3;
  ChargeTo charge_to = 5;
}

message GetLeavesByHashResponse {
  repeated LogLeaf leaves = 2;
  SignedLogRoot signed_log_root = 3;
}

// QueuedLogLeaf provides the result of submitting an entry to the log.
// TODO(pavelkalinnikov): Consider renaming it to AddLogLeafResult or the like.
message QueuedLogLeaf {
  // The leaf as it was stored by Trillian. Empty unless `status.code` is:
  //  - `google.rpc.OK`: the `leaf` data is the same as in the request.
  //  - `google.rpc.ALREADY_EXISTS` or 'google.rpc.FAILED_PRECONDITION`: the
  //    `leaf` is the conflicting one already in the log.
  LogLeaf leaf = 1;

  // The status of adding the leaf.
  //  - `google.rpc.OK`: successfully added.
  //  - `google.rpc.ALREADY_EXISTS`: the leaf is a duplicate of an already
  //    existing one. Either `leaf_identity_hash` is the same in the `LOG`
  //    mode, or `leaf_index` in the `PREORDERED_LOG`.
  //  - `google.rpc.FAILED_PRECONDITION`: A conflicting entry is already
  //    present in the log, e.g., same `leaf_index` but different `leaf_data`.
  google.rpc.Status status = 2;
}

// LogLeaf describes a leaf in the Log's Merkle tree, corresponding to a single log entry.
// Each leaf has a unique leaf index in the scope of this tree.  Clients submitting new
// leaf entries should only set the following fields:
//   - leaf_value
//   - extra_data (optionally)
//   - leaf_identity_hash (optionally)
//   - leaf_index (iff the log is a PREORDERED_LOG)
message LogLeaf {
  // merkle_leaf_hash holds the Merkle leaf hash over leaf_value.  This is
  // calculated by the Trillian server when leaves are added to the tree, using
  // the defined hashing algorithm and strategy for the tree; as such, the client
  // does not need to set it on leaf submissions.
  bytes merkle_leaf_hash = 1;

  // leaf_value holds the data that forms the value of the Merkle tree leaf.
  // The client should set this field on all leaf submissions, and is
  // responsible for ensuring its validity (the Trillian server treats it as an
  // opaque blob).
  bytes leaf_value = 2;

  // extra_data holds additional data associated with the Merkle tree leaf.
  // The client may set this data on leaf submissions, and the Trillian server
  // will return it on subsequent read operations. However, the contents of
  // this field are not covered by and do not affect the Merkle tree hash
  // calculations.
  bytes extra_data = 3;

  // leaf_index indicates the index of this leaf in the Merkle tree.
  // This field is returned on all read operations, but should only be
  // set for leaf submissions in PREORDERED_LOG mode (for a normal log
  // the leaf index is assigned by Trillian when the submitted leaf is
  // integrated into the Merkle tree).
  int64 leaf_index = 4;

  // leaf_identity_hash provides a hash value that indicates the client's
  // concept of which leaf entries should be considered identical.
  //
  // This mechanism allows the client personality to indicate that two leaves
  // should be considered "duplicates" even though their `leaf_value`s differ.
  //
  // If this is not set on leaf submissions, the Trillian server will take its
  // value to be the same as merkle_leaf_hash (and thus only leaves with
  // identical leaf_value contents will be considered identical).
  //
  // For example, in Certificate Transparency each certificate submission is
  // associated with a submission timestamp, but subsequent submissions of the
  // same certificate should be considered identical.  This is achieved
  // by setting the leaf identity hash to a hash over (just) the certificate,
  // whereas the Merkle leaf hash encompasses both the certificate and its
  // submission time -- allowing duplicate certificates to be detected.
  //
  //
  // Continuing the CT example, for a CT mirror personality (which must allow
  // dupes since the source log could contain them), the part of the
  // personality which fetches and submits the entries might set
  // `leaf_identity_hash` to `H(leaf_index||cert)`.
  //
  // TODO(pavelkalinnikov): Consider instead using `H(cert)` and allowing
  // identity hash dupes in `PREORDERED_LOG` mode, for it can later be
  // upgraded to `LOG` which will need to correctly detect duplicates with
  // older entries when new ones get queued.
  bytes leaf_identity_hash = 5;

  // queue_timestamp holds the time at which this leaf was queued for
  // inclusion in the Log, or zero if the entry was submitted without
  // queuing. Clients should not set this field on submissions.
  google.protobuf.Timestamp queue_timestamp = 6;

  // integrate_timestamp holds the time at which this leaf was integrated into
  // the tree.  Clients should not set this field on submissions.
  google.protobuf.Timestamp integrate_timestamp = 7;
}
