/*
	Copyright The containerd Authors.

	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 containerd.services.content.v1;

import weak "gogoproto/gogo.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

option go_package = "github.com/containerd/containerd/api/services/content/v1;content";

// Content provides access to a content addressable storage system.
service Content {
	// Info returns information about a committed object.
	//
	// This call can be used for getting the size of content and checking for
	// existence.
	rpc Info(InfoRequest) returns (InfoResponse);

	// Update updates content metadata.
	//
	// This call can be used to manage the mutable content labels. The
	// immutable metadata such as digest, size, and committed at cannot
	// be updated.
	rpc Update(UpdateRequest) returns (UpdateResponse);

	// List streams the entire set of content as Info objects and closes the
	// stream.
	//
	// Typically, this will yield a large response, chunked into messages.
	// Clients should make provisions to ensure they can handle the entire data
	// set.
	rpc List(ListContentRequest) returns (stream ListContentResponse);

	// Delete will delete the referenced object.
	rpc Delete(DeleteContentRequest) returns (google.protobuf.Empty);

	// Read allows one to read an object based on the offset into the content.
	//
	// The requested data may be returned in one or more messages.
	rpc Read(ReadContentRequest) returns (stream ReadContentResponse);

	// Status returns the status for a single reference.
	rpc Status(StatusRequest) returns (StatusResponse);

	// ListStatuses returns the status of ongoing object ingestions, started via
	// Write.
	//
	// Only those matching the regular expression will be provided in the
	// response. If the provided regular expression is empty, all ingestions
	// will be provided.
	rpc ListStatuses(ListStatusesRequest) returns (ListStatusesResponse);

	// Write begins or resumes writes to a resource identified by a unique ref.
	// Only one active stream may exist at a time for each ref.
	//
	// Once a write stream has started, it may only write to a single ref, thus
	// once a stream is started, the ref may be omitted on subsequent writes.
	//
	// For any write transaction represented by a ref, only a single write may
	// be made to a given offset. If overlapping writes occur, it is an error.
	// Writes should be sequential and implementations may throw an error if
	// this is required.
	//
	// If expected_digest is set and already part of the content store, the
	// write will fail.
	//
	// When completed, the commit flag should be set to true. If expected size
	// or digest is set, the content will be validated against those values.
	rpc Write(stream WriteContentRequest) returns (stream WriteContentResponse);

	// Abort cancels the ongoing write named in the request. Any resources
	// associated with the write will be collected.
	rpc Abort(AbortRequest) returns (google.protobuf.Empty);
}

message Info {
	// Digest is the hash identity of the blob.
	string digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];

	// Size is the total number of bytes in the blob.
	int64 size = 2;

	// CreatedAt provides the time at which the blob was committed.
	google.protobuf.Timestamp created_at = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];

	// UpdatedAt provides the time the info was last updated.
	google.protobuf.Timestamp updated_at = 4 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];

	// Labels are arbitrary data on snapshots.
	//
	// The combined size of a key/value pair cannot exceed 4096 bytes.
	map<string, string> labels  = 5;
}

message InfoRequest {
	string digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
}

message InfoResponse {
	Info info = 1 [(gogoproto.nullable) = false];
}

message UpdateRequest {
	Info info = 1 [(gogoproto.nullable) = false];

	// UpdateMask specifies which fields to perform the update on. If empty,
	// the operation applies to all fields.
	//
	// In info, Digest, Size, and CreatedAt are immutable,
	// other field may be updated using this mask.
	// If no mask is provided, all mutable field are updated.
	google.protobuf.FieldMask update_mask = 2;
}

message UpdateResponse {
	Info info = 1 [(gogoproto.nullable) = false];
}

message ListContentRequest {
	// Filters contains one or more filters using the syntax defined in the
	// containerd filter package.
	//
	// The returned result will be those that match any of the provided
	// filters. Expanded, containers that match the following will be
	// returned:
	//
	//	filters[0] or filters[1] or ... or filters[n-1] or filters[n]
	//
	// If filters is zero-length or nil, all items will be returned.
	repeated string filters = 1;
}

message ListContentResponse {
	repeated Info info = 1 [(gogoproto.nullable) = false];
}

message DeleteContentRequest {
	// Digest specifies which content to delete.
	string digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
}

// ReadContentRequest defines the fields that make up a request to read a portion of
// data from a stored object.
message ReadContentRequest {
	// Digest is the hash identity to read.
	string digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];

	// Offset specifies the number of bytes from the start at which to begin
	// the read. If zero or less, the read will be from the start. This uses
	// standard zero-indexed semantics.
	int64 offset = 2;

	// size is the total size of the read. If zero, the entire blob will be
	// returned by the service.
	int64 size = 3;
}

// ReadContentResponse carries byte data for a read request.
message ReadContentResponse {
	int64 offset = 1; // offset of the returned data
	bytes data = 2; // actual data
}

message Status {
	google.protobuf.Timestamp started_at = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
	google.protobuf.Timestamp updated_at = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
	string ref = 3;
	int64 offset = 4;
	int64 total = 5;
	string expected = 6 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
}


message StatusRequest {
	string ref = 1;
}

message StatusResponse {
	Status status = 1;
}

message ListStatusesRequest {
	repeated string filters = 1;
}

message ListStatusesResponse {
	repeated Status statuses = 1 [(gogoproto.nullable) = false];
}

// WriteAction defines the behavior of a WriteRequest.
enum WriteAction {
	option (gogoproto.goproto_enum_prefix) = false;
	option (gogoproto.enum_customname) = "WriteAction";

	// WriteActionStat instructs the writer to return the current status while
	// holding the lock on the write.
	STAT = 0 [(gogoproto.enumvalue_customname) = "WriteActionStat"];

	// WriteActionWrite sets the action for the write request to write data.
	//
	// Any data included will be written at the provided offset. The
	// transaction will be left open for further writes.
	//
	// This is the default.
	WRITE = 1 [(gogoproto.enumvalue_customname) = "WriteActionWrite"];

	// WriteActionCommit will write any outstanding data in the message and
	// commit the write, storing it under the digest.
	//
	// This can be used in a single message to send the data, verify it and
	// commit it.
	//
	// This action will always terminate the write.
	COMMIT = 2 [(gogoproto.enumvalue_customname) = "WriteActionCommit"];
}

// WriteContentRequest writes data to the request ref at offset.
message WriteContentRequest {
	// Action sets the behavior of the write.
	//
	// When this is a write and the ref is not yet allocated, the ref will be
	// allocated and the data will be written at offset.
	//
	// If the action is write and the ref is allocated, it will accept data to
	// an offset that has not yet been written.
	//
	// If the action is write and there is no data, the current write status
	// will be returned. This works differently from status because the stream
	// holds a lock.
	WriteAction action = 1;

	// Ref identifies the pre-commit object to write to.
	string ref = 2;

	// Total can be set to have the service validate the total size of the
	// committed content.
	//
	// The latest value before or with the commit action message will be use to
	// validate the content. If the offset overflows total, the service may
	// report an error. It is only required on one message for the write.
	//
	// If the value is zero or less, no validation of the final content will be
	// performed.
	int64 total = 3;

	// Expected can be set to have the service validate the final content against
	// the provided digest.
	//
	// If the digest is already present in the object store, an AlreadyExists
	// error will be returned.
	//
	// Only the latest version will be used to check the content against the
	// digest. It is only required to include it on a single message, before or
	// with the commit action message.
	string expected = 4 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];

	// Offset specifies the number of bytes from the start at which to begin
	// the write. For most implementations, this means from the start of the
	// file. This uses standard, zero-indexed semantics.
	//
	// If the action is write, the remote may remove all previously written
	// data after the offset. Implementations may support arbitrary offsets but
	// MUST support reseting this value to zero with a write. If an
	// implementation does not support a write at a particular offset, an
	// OutOfRange error must be returned.
	int64 offset = 5;

	// Data is the actual bytes to be written.
	//
	// If this is empty and the message is not a commit, a response will be
	// returned with the current write state.
	bytes data = 6;

	// Labels are arbitrary data on snapshots.
	//
	// The combined size of a key/value pair cannot exceed 4096 bytes.
	map<string, string> labels  = 7;
}

// WriteContentResponse is returned on the culmination of a write call.
message WriteContentResponse {
	// Action contains the action for the final message of the stream. A writer
	// should confirm that they match the intended result.
	WriteAction action = 1;

	// StartedAt provides the time at which the write began.
	//
	// This must be set for stat and commit write actions. All other write
	// actions may omit this.
	google.protobuf.Timestamp started_at = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];

	// UpdatedAt provides the last time of a successful write.
	//
	// This must be set for stat and commit write actions. All other write
	// actions may omit this.
	google.protobuf.Timestamp updated_at = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];

	// Offset is the current committed size for the write.
	int64 offset = 4;

	// Total provides the current, expected total size of the write.
	//
	// We include this to provide consistency with the Status structure on the
	// client writer.
	//
	// This is only valid on the Stat and Commit response.
	int64 total = 5;

	// Digest, if present, includes the digest up to the currently committed
	// bytes. If action is commit, this field will be set. It is implementation
	// defined if this is set for other actions.
	string digest = 6 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
}

message AbortRequest {
	string ref = 1;
}
