forked from TrueCloudLab/frostfs-api
[#216] English Check
Signed-off-by: Elizaveta Chichindaeva <elizaveta@nspcc.ru>
This commit is contained in:
parent
431335054c
commit
f233a2fd67
22 changed files with 156 additions and 156 deletions
|
@ -44,8 +44,8 @@ $ git merge upstream/master
|
|||
```
|
||||
|
||||
### Create your feature branch
|
||||
Before making code changes, make sure you create a separate branch for these
|
||||
changes. Maybe you will find it convenient to name branch in
|
||||
Before making code changes, make sure you have created a separate branch for these
|
||||
changes. Maybe you will find it convenient to name branch in the
|
||||
`<type>/<Issue>-<changes_topic>` format.
|
||||
|
||||
```sh
|
||||
|
@ -106,9 +106,9 @@ To sign your work, just add a line like this at the end of your commit message:
|
|||
Signed-off-by: Samii Sakisaka <samii@nspcc.ru>
|
||||
```
|
||||
|
||||
This can easily be done with the `--signoff` option to `git commit`.
|
||||
This can be done easily with the `--signoff` option to `git commit`.
|
||||
|
||||
By doing this you state that you can certify the following (from [The Developer
|
||||
By doing this, you state that you can certify the following (from [The Developer
|
||||
Certificate of Origin](https://developercertificate.org/)):
|
||||
|
||||
```
|
||||
|
|
|
@ -29,7 +29,7 @@ This repository contains:
|
|||
Feel free to contribute to this project after reading the [contributing
|
||||
guidelines](CONTRIBUTING.md).
|
||||
|
||||
Before starting to work on a certain topic, create a new issue first,
|
||||
Before you start working on a certain topic, first create a new issue
|
||||
describing the feature/topic you are going to implement.
|
||||
|
||||
## License
|
||||
|
|
|
@ -13,7 +13,7 @@ import "session/types.proto";
|
|||
// other NeoFS nodes to get information about the account balance. Deposit and
|
||||
// Withdraw operations can't be implemented here, as they require Mainnet NeoFS
|
||||
// smart contract invocation. Transfer operations between internal NeoFS
|
||||
// accounts are possible, if both use the same token type.
|
||||
// accounts are possible if both use the same token type.
|
||||
service AccountingService {
|
||||
// Returns the amount of funds in GAS token for the requested NeoFS account.
|
||||
//
|
||||
|
@ -26,7 +26,7 @@ service AccountingService {
|
|||
|
||||
// BalanceRequest message
|
||||
message BalanceRequest {
|
||||
// To indicate the account for which the balance is requested, it's identifier
|
||||
// To indicate the account for which the balance is requested, its identifier
|
||||
// is used. It can be any existing account in NeoFS sidechain `Balance` smart
|
||||
// contract. If omitted, client implementation MUST set it to the request's
|
||||
// signer `OwnerID`.
|
||||
|
@ -51,7 +51,7 @@ message BalanceRequest {
|
|||
// BalanceResponse message
|
||||
message BalanceResponse {
|
||||
// The amount of funds in GAS token for the `OwnerID`'s account requested.
|
||||
// Balance is `Decimal` format to avoid precision issues with rounding.
|
||||
// Balance is given in the `Decimal` format to avoid precision issues with rounding.
|
||||
message Body {
|
||||
// Amount of funds in GAS token for the requested account.
|
||||
Decimal balance = 1;
|
||||
|
|
|
@ -13,7 +13,7 @@ option csharp_namespace = "Neo.FileStorage.API.Accounting";
|
|||
// Specification](http://speleotrove.com/decimal/) for detailed problem
|
||||
// description.
|
||||
message Decimal {
|
||||
// Number in smallest Token fractions.
|
||||
// Number in the smallest Token fractions.
|
||||
int64 value = 1 [json_name = "value"];
|
||||
|
||||
// Precision value indicating how many smallest fractions can be in one
|
||||
|
|
|
@ -15,11 +15,11 @@ enum Role {
|
|||
// User target rule is applied if sender is the owner of the container
|
||||
USER = 1;
|
||||
|
||||
// System target rule is applied if sender is the storage node within the
|
||||
// container or inner ring node
|
||||
// System target rule is applied if sender is a storage node within the
|
||||
// container or an inner ring node
|
||||
SYSTEM = 2;
|
||||
|
||||
// Others target rule is applied if sender is not user nor system target
|
||||
// Others target rule is applied if sender is neither a user nor a system target
|
||||
OTHERS = 3;
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ message EACLRecord {
|
|||
// Rule execution result. Either allows or denies access if filters match.
|
||||
Action action = 2 [json_name = "action"];
|
||||
|
||||
// Filter to check particular properties of the request or object.
|
||||
// Filter to check particular properties of the request or the object.
|
||||
//
|
||||
// By default `key` field refers to the corresponding object's `Attribute`.
|
||||
// Some Object's header fields can also be accessed by adding `$Object:`
|
||||
|
@ -160,12 +160,12 @@ message EACLRecord {
|
|||
repeated Target targets = 4 [json_name="targets"];
|
||||
}
|
||||
|
||||
// Extended ACL rules table. Defined a list of ACL rules additionally to Basic
|
||||
// ACL. Extended ACL rules can be attached to the container and can be updated
|
||||
// Extended ACL rules table. A list of ACL rules defined additionally to Basic
|
||||
// ACL. Extended ACL rules can be attached to a container and can be updated
|
||||
// or may be defined in `BearerToken` structure. Please see the corresponding
|
||||
// NeoFS Technical Specification's section for detailed description.
|
||||
// NeoFS Technical Specification section for detailed description.
|
||||
message EACLTable {
|
||||
// eACL format version. Effectively the version of API library used to create
|
||||
// eACL format version. Effectively, the version of API library used to create
|
||||
// eACL Table.
|
||||
neo.fs.v2.refs.Version version = 1 [json_name = "version"];
|
||||
|
||||
|
@ -183,11 +183,11 @@ message EACLTable {
|
|||
// used in the similar use cases, like providing authorisation to externally
|
||||
// authenticated party.
|
||||
//
|
||||
// BearerToken can be issued only by container's owner and must be signed using
|
||||
// the key associated with container's `OwnerID`.
|
||||
// BearerToken can be issued only by the container's owner and must be signed using
|
||||
// the key associated with the container's `OwnerID`.
|
||||
message BearerToken {
|
||||
// Bearer Token body structure contains Extended ACL table issued by container
|
||||
// owner with additional information preventing token's abuse.
|
||||
// Bearer Token body structure contains Extended ACL table issued by the container
|
||||
// owner with additional information preventing token abuse.
|
||||
message Body {
|
||||
// Table of Extended ACL rules to use instead of the ones attached to the
|
||||
// container. If it contains `container_id` field, bearer token is only
|
||||
|
@ -195,7 +195,7 @@ message BearerToken {
|
|||
// is allowed.
|
||||
EACLTable eacl_table = 1 [json_name="eaclTable"];
|
||||
|
||||
// `OwnerID` to whom the token was issued. Must match the request
|
||||
// `OwnerID` defines to whom the token was issued. It must match the request
|
||||
// originator's `OwnerID`. If empty, any token bearer will be accepted.
|
||||
neo.fs.v2.refs.OwnerID owner_id = 2 [json_name="ownerID"];
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import "refs/types.proto";
|
|||
// DataAuditResult keeps record of conducted Data Audits. The detailed report is
|
||||
// generated separately.
|
||||
message DataAuditResult {
|
||||
// Data Audit Result format version. Effectively the version of API library
|
||||
// Data Audit Result format version. Effectively, the version of API library
|
||||
// used to report DataAuditResult structure.
|
||||
neo.fs.v2.refs.Version version = 1 [json_name = "version"];
|
||||
|
||||
|
@ -38,16 +38,16 @@ message DataAuditResult {
|
|||
// List of Storage Groups that failed audit PoR stage
|
||||
repeated neo.fs.v2.refs.ObjectID fail_sg = 9 [json_name = "failSG"];
|
||||
|
||||
// Number of sampled objects under audit placed in an optimal way according to
|
||||
// Number of sampled objects under the audit placed in an optimal way according to
|
||||
// the containers placement policy when checking PoP
|
||||
uint32 hit = 10 [json_name = "hit"];
|
||||
|
||||
// Number of sampled objects under audit placed in suboptimal way according to
|
||||
// Number of sampled objects under the audit placed in suboptimal way according to
|
||||
// the containers placement policy, but still at a satisfactory level when
|
||||
// checking PoP
|
||||
uint32 miss = 11 [json_name = "miss"];
|
||||
|
||||
// Number of sampled objects under audit stored in a way not confirming
|
||||
// Number of sampled objects under the audit stored inconsistently with the
|
||||
// placement policy or not found at all when checking PoP
|
||||
uint32 fail = 12 [json_name = "fail"];
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import "session/types.proto";
|
|||
service ContainerService {
|
||||
// `Put` invokes `Container` smart contract's `Put` method and returns
|
||||
// response immediately. After a new block is issued in sidechain, request is
|
||||
// verified by Inner Ring nodes. After one more block in sidechain, container
|
||||
// verified by Inner Ring nodes. After one more block in sidechain, the container
|
||||
// is added into smart contract storage.
|
||||
//
|
||||
// Statuses:
|
||||
|
@ -28,7 +28,7 @@ service ContainerService {
|
|||
|
||||
// `Delete` invokes `Container` smart contract's `Delete` method and returns
|
||||
// response immediately. After a new block is issued in sidechain, request is
|
||||
// verified by Inner Ring nodes. After one more block in sidechain, container
|
||||
// verified by Inner Ring nodes. After one more block in sidechain, the container
|
||||
// is added into smart contract storage.
|
||||
//
|
||||
// Statuses:
|
||||
|
@ -56,7 +56,7 @@ service ContainerService {
|
|||
rpc List(ListRequest) returns (ListResponse);
|
||||
|
||||
// Invokes 'SetEACL' method of 'Container` smart contract and returns response
|
||||
// immediately. After one more block in sidechain, Extended ACL changes are
|
||||
// immediately. After one more block in sidechain, changes in an Extended ACL are
|
||||
// added into smart contract storage.
|
||||
//
|
||||
// Statuses:
|
||||
|
@ -76,7 +76,7 @@ service ContainerService {
|
|||
// container not found.
|
||||
rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse);
|
||||
|
||||
// Announce container used space values for P2P synchronization.
|
||||
// Announces the space values used by the container for P2P synchronization.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS): \
|
||||
|
@ -117,7 +117,7 @@ message PutResponse {
|
|||
// Container put response body contains information about the newly registered
|
||||
// container as seen by `Container` smart contract. `ContainerID` can be
|
||||
// calculated beforehand from the container structure and compared to the one
|
||||
// returned here to make sure everything was done as expected.
|
||||
// returned here to make sure everything has been done as expected.
|
||||
message Body {
|
||||
// Unique identifier of the newly created container
|
||||
neo.fs.v2.refs.ContainerID container_id = 1;
|
||||
|
@ -137,8 +137,8 @@ message PutResponse {
|
|||
|
||||
// Container removal request
|
||||
message DeleteRequest {
|
||||
// Container removal request body has a signed `ContainerID` as a proof of
|
||||
// container owner's intent. The signature will be verified by `Container`
|
||||
// Container removal request body has signed `ContainerID` as a proof of
|
||||
// the container owner's intent. The signature will be verified by `Container`
|
||||
// smart contract, so signing algorithm must be supported by NeoVM.
|
||||
message Body {
|
||||
// Identifier of the container to delete from NeoFS
|
||||
|
@ -202,7 +202,7 @@ message GetRequest {
|
|||
// Get container structure
|
||||
message GetResponse {
|
||||
// Get container response body does not have container structure signature. It
|
||||
// was already verified on container creation.
|
||||
// has been already verified upon container creation.
|
||||
message Body {
|
||||
// Requested container structure
|
||||
Container container = 1;
|
||||
|
@ -210,7 +210,7 @@ message GetResponse {
|
|||
// Signature of a stable-marshalled container according to RFC-6979.
|
||||
neo.fs.v2.refs.SignatureRFC6979 signature = 2;
|
||||
|
||||
// Session token if the container was created within a session
|
||||
// Session token if the container has been created within the session
|
||||
neo.fs.v2.session.SessionToken session_token = 3;
|
||||
}
|
||||
// Body of container get response message.
|
||||
|
@ -272,7 +272,7 @@ message SetExtendedACLRequest {
|
|||
// Set Extended ACL request body does not have separate `ContainerID`
|
||||
// reference. It will be taken from `EACLTable.container_id` field.
|
||||
message Body {
|
||||
// Extended ACL table to set for container
|
||||
// Extended ACL table to set for the container
|
||||
neo.fs.v2.acl.EACLTable eacl = 1;
|
||||
|
||||
// Signature of stable-marshalled Extended ACL table according to RFC-6979.
|
||||
|
@ -294,7 +294,7 @@ message SetExtendedACLRequest {
|
|||
// Set Extended ACL
|
||||
message SetExtendedACLResponse {
|
||||
// `SetExtendedACLResponse` has an empty body because the operation is
|
||||
// asynchronous and update should be reflected in `Container` smart contract's
|
||||
// asynchronous and the update should be reflected in `Container` smart contract's
|
||||
// storage after next block is issued in sidechain.
|
||||
message Body { }
|
||||
|
||||
|
@ -334,9 +334,9 @@ message GetExtendedACLRequest {
|
|||
|
||||
// Get Extended ACL
|
||||
message GetExtendedACLResponse {
|
||||
// Get Extended ACL Response body can be empty if the requested container did
|
||||
// not have Extended ACL Table attached or Extended ACL was not allowed at
|
||||
// container creation.
|
||||
// Get Extended ACL Response body can be empty if the requested container does
|
||||
// not have Extended ACL Table attached or Extended ACL has not been allowed at
|
||||
// the time of container creation.
|
||||
message Body {
|
||||
// Extended ACL requested, if available
|
||||
neo.fs.v2.acl.EACLTable eacl = 1;
|
||||
|
@ -364,21 +364,21 @@ message GetExtendedACLResponse {
|
|||
message AnnounceUsedSpaceRequest {
|
||||
// Container used space announcement body.
|
||||
message Body {
|
||||
// Announcement contains used space information about single container.
|
||||
// Announcement contains used space information for a single container.
|
||||
message Announcement {
|
||||
// Epoch number for which container size estimation was produced.
|
||||
// Epoch number for which the container size estimation was produced.
|
||||
uint64 epoch = 1;
|
||||
|
||||
// Identifier of the container.
|
||||
neo.fs.v2.refs.ContainerID container_id = 2;
|
||||
|
||||
// Used space is a sum of object payload sizes of specified
|
||||
// Used space is a sum of object payload sizes of a specified
|
||||
// container, stored in the node. It must not include inhumed objects.
|
||||
uint64 used_space = 3;
|
||||
}
|
||||
|
||||
// List of announcements. If nodes share several containers, then
|
||||
// announcements transferred in a batch.
|
||||
// List of announcements. If nodes share several containers,
|
||||
// announcements are transferred in a batch.
|
||||
repeated Announcement announcements = 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@ import "refs/types.proto";
|
|||
|
||||
// Container is a structure that defines object placement behaviour. Objects can
|
||||
// be stored only within containers. They define placement rule, attributes and
|
||||
// access control information. ID of the container is a 32 byte long SHA256 hash
|
||||
// access control information. An ID of a container is a 32 byte long SHA256 hash
|
||||
// of stable-marshalled container message.
|
||||
message Container {
|
||||
// Container format version. Effectively the version of API library used to
|
||||
// create container.
|
||||
// Container format version. Effectively, the version of API library used to
|
||||
// create the container.
|
||||
neo.fs.v2.refs.Version version = 1 [json_name = "version"];
|
||||
|
||||
// Identifier of the container owner
|
||||
|
@ -23,13 +23,13 @@ message Container {
|
|||
// Nonce is a 16 byte UUIDv4, used to avoid collisions of `ContainerID`s
|
||||
bytes nonce = 3 [json_name = "nonce"];
|
||||
|
||||
// `BasicACL` contains access control rules for owner, system, others groups
|
||||
// and permission bits for `BearerToken` and `Extended ACL`
|
||||
// `BasicACL` contains access control rules for the owner, system and others groups,
|
||||
// as well as permission bits for `BearerToken` and `Extended ACL`
|
||||
uint32 basic_acl = 4 [json_name = "basicACL"];
|
||||
|
||||
// `Attribute` is a user-defined Key-Value metadata pair attached to the
|
||||
// container. Container attributes are immutable. They are set at container
|
||||
// creation and can never be added or updated.
|
||||
// container. Container attributes are immutable. They are set at the moment of
|
||||
// container creation and can never be added or updated.
|
||||
//
|
||||
// Key name must be a container-unique valid UTF-8 string. Value can't be
|
||||
// empty. Containers with duplicated attribute names or attributes with empty
|
||||
|
@ -38,14 +38,14 @@ message Container {
|
|||
// There are some "well-known" attributes affecting system behaviour:
|
||||
//
|
||||
// * __NEOFS__SUBNET \
|
||||
// String ID of container's storage subnet. Container can be attached to
|
||||
// only one subnet.
|
||||
// String ID of a container's storage subnet. Any container can be attached to
|
||||
// one subnet only.
|
||||
// * __NEOFS__NAME \
|
||||
// String of human-friendly container name registered as the domain in
|
||||
// String of a human-friendly container name registered as a domain in
|
||||
// NNS contract.
|
||||
// * __NEOFS__ZONE \
|
||||
// String of zone for `__NEOFS__NAME`. Used as TLD of domain name in NNS
|
||||
// contract. If zone is not specified, use default zone: `container`.
|
||||
// String of a zone for `__NEOFS__NAME`. Used as a TLD of a domain name in NNS
|
||||
// contract. If no zone is specified, use default zone: `container`.
|
||||
//
|
||||
// And some well-known attributes used by applications only:
|
||||
//
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
# Release instructions
|
||||
|
||||
This documents outlines the neofs-api release process, and can be used as a TODO
|
||||
This documents outlines the neofs-api release process and can be used as a TODO
|
||||
list for a new release.
|
||||
|
||||
## Pre-release checks
|
||||
|
||||
These should run successfully:
|
||||
This should run successfully:
|
||||
* `make lint`
|
||||
|
||||
## Pre-release actions
|
||||
|
||||
These must be run:
|
||||
This must be run:
|
||||
* `make doc`
|
||||
|
||||
## Writing CHANGELOG
|
||||
|
||||
Add an entry to the CHANGELOG.md following the style established there.
|
||||
|
||||
Add a codename for releases with new major version, version and release date in
|
||||
Add a codename for releases with the new major version, version and release date in
|
||||
the heading. Write a paragraph describing the most significant changes done in
|
||||
this release. Then add sections with what was added, changed and removed,
|
||||
this release. Then add sections with what has been added, changed and removed,
|
||||
describing each change briefly with a reference to GitHub issues, where
|
||||
available.
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ option csharp_namespace = "Neo.FileStorage.API.Lock";
|
|||
|
||||
import "refs/types.proto";
|
||||
|
||||
// Lock objects protects a list of objects from being deleted. Lifetime of the
|
||||
// Lock objects protects a list of objects from being deleted. The lifetime of a
|
||||
// lock object is limited similar to regular objects in
|
||||
// `__NEOFS__EXPIRATION_EPOCH` attribute.
|
||||
message Lock {
|
||||
|
|
|
@ -9,16 +9,16 @@ import "netmap/types.proto";
|
|||
import "refs/types.proto";
|
||||
import "session/types.proto";
|
||||
|
||||
// `NetmapService` provides methods to work with `Network Map` and information
|
||||
// `NetmapService` provides methods to work with `Network Map` and the information
|
||||
// required to build it. The resulting `Network Map` is stored in sidechain
|
||||
// `Netmap` smart contract, while related information can be obtained from other
|
||||
// NeoFS nodes.
|
||||
service NetmapService {
|
||||
// Get NodeInfo structure from the particular node directly. Node information
|
||||
// can be taken from `Netmap` smart contract, but in some cases the one may
|
||||
// want to get recent information directly, or to talk to the node not yet
|
||||
// present in `Network Map` to find out what API version can be used for
|
||||
// further communication. Can also be used to check if node is up and running.
|
||||
// Get NodeInfo structure from the particular node directly.
|
||||
// Node information can be taken from `Netmap` smart contract. In some cases, though,
|
||||
// one may want to get recent information directly or to talk to the node not yet
|
||||
// present in the `Network Map` to find out what API version can be used for
|
||||
// further communication. This can be also used to check if a node is up and running.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS):
|
||||
|
@ -35,7 +35,7 @@ service NetmapService {
|
|||
rpc NetworkInfo (NetworkInfoRequest) returns (NetworkInfoResponse);
|
||||
}
|
||||
|
||||
// Get NodeInfo structure from the particular node directly
|
||||
// Get NodeInfo structure directly from a particular node
|
||||
message LocalNodeInfoRequest {
|
||||
// LocalNodeInfo request body is empty.
|
||||
message Body {
|
||||
|
@ -76,7 +76,7 @@ message LocalNodeInfoResponse {
|
|||
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
|
||||
}
|
||||
|
||||
// Get NetworkInfo structure with the network view from particular node.
|
||||
// Get NetworkInfo structure with the network view from a particular node.
|
||||
message NetworkInfoRequest {
|
||||
// NetworkInfo request body is empty.
|
||||
message Body {
|
||||
|
|
|
@ -41,7 +41,7 @@ enum Operation {
|
|||
// just groups nodes into a bucket by attribute, selecting nodes only by their
|
||||
// hash distance.
|
||||
enum Clause {
|
||||
// No modifier defined. Will select nodes from bucket randomly.
|
||||
// No modifier defined. Nodes will be selected from the bucket randomly
|
||||
CLAUSE_UNSPECIFIED = 0;
|
||||
|
||||
// SAME will select only nodes having the same value of bucket attribute
|
||||
|
@ -51,10 +51,10 @@ enum Clause {
|
|||
DISTINCT = 2;
|
||||
}
|
||||
|
||||
// Filter will return the subset of nodes from `NetworkMap` or another filter's
|
||||
// results, that will satisfy filter's conditions.
|
||||
// This filter will return the subset of nodes from `NetworkMap` or another filter's
|
||||
// results that will satisfy filter's conditions.
|
||||
message Filter {
|
||||
// Name of the filter or a reference to the named filter. '*' means
|
||||
// Name of the filter or a reference to a named filter. '*' means
|
||||
// application to the whole unfiltered NetworkMap. At top level it's used as a
|
||||
// filter name. At lower levels it's considered to be a reference to another
|
||||
// named filter
|
||||
|
@ -86,7 +86,7 @@ message Selector {
|
|||
// Selector modifier showing how to form a bucket
|
||||
Clause clause = 3 [json_name = "clause"];
|
||||
|
||||
// Attribute bucket to select from
|
||||
// Bucket attribute to select from
|
||||
string attribute = 4 [json_name = "attribute"];
|
||||
|
||||
// Filter reference to select from
|
||||
|
@ -94,7 +94,7 @@ message Selector {
|
|||
}
|
||||
|
||||
// Number of object replicas in a set of nodes from the defined selector. If no
|
||||
// selector set the root bucket containing all possible nodes will be used by
|
||||
// selector set, the root bucket containing all possible nodes will be used by
|
||||
// default.
|
||||
message Replica {
|
||||
// How many object replicas to put
|
||||
|
@ -204,7 +204,7 @@ message NodeInfo {
|
|||
// automatically from `UN-LOCODE` attribute.
|
||||
//
|
||||
// For detailed description of each well-known attribute please see the
|
||||
// corresponding section in NeoFS Technical specification.
|
||||
// corresponding section in NeoFS Technical Specification.
|
||||
message Attribute {
|
||||
// Key of the node attribute
|
||||
string key = 1 [json_name = "key"];
|
||||
|
|
|
@ -10,14 +10,14 @@ import "refs/types.proto";
|
|||
import "session/types.proto";
|
||||
|
||||
// `ObjectService` provides API for manipulating objects. Object operations do
|
||||
// not interact with sidechain and are only served by nodes in p2p style.
|
||||
// not affect the sidechain and are only served by nodes in p2p style.
|
||||
service ObjectService {
|
||||
// Receive full object structure, including Headers and payload. Response uses
|
||||
// gRPC stream. First response message carries object with requested address.
|
||||
// gRPC stream. First response message carries the object with the requested address.
|
||||
// Chunk messages are parts of the object's payload if it is needed. All
|
||||
// messages, except the first one, carry payload chunks. Requested object can
|
||||
// messages, except the first one, carry payload chunks. The requested object can
|
||||
// be restored by concatenation of object message payload and all chunks
|
||||
// keeping receiving order.
|
||||
// keeping the receiving order.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS): \
|
||||
|
@ -40,7 +40,7 @@ service ObjectService {
|
|||
// SHOULD be set. Session token SHOULD be obtained before `PUT` operation (see
|
||||
// session package). Chunk messages are considered by server as a part of an
|
||||
// object payload. All messages, except first one, SHOULD be payload chunks.
|
||||
// Chunk messages SHOULD be sent in direct order of fragmentation.
|
||||
// Chunk messages SHOULD be sent in the direct order of fragmentation.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS): \
|
||||
|
@ -82,7 +82,7 @@ service ObjectService {
|
|||
|
||||
// Returns the object Headers without data payload. By default full header is
|
||||
// returned. If `main_only` request field is set, the short header with only
|
||||
// the very minimal information would be returned instead.
|
||||
// the very minimal information will be returned instead.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS): \
|
||||
|
@ -118,7 +118,7 @@ service ObjectService {
|
|||
|
||||
// Get byte range of data payload. Range is set as an (offset, length) tuple.
|
||||
// Like in `Get` method, the response uses gRPC stream. Requested range can be
|
||||
// restored by concatenation of all received payload chunks keeping receiving
|
||||
// restored by concatenation of all received payload chunks keeping the receiving
|
||||
// order.
|
||||
//
|
||||
// Statuses:
|
||||
|
@ -139,8 +139,8 @@ service ObjectService {
|
|||
|
||||
// Returns homomorphic or regular hash of object's payload range after
|
||||
// applying XOR operation with the provided `salt`. Ranges are set of (offset,
|
||||
// length) tuples. Hashes order in response corresponds to ranges order in
|
||||
// request. Note that hash is calculated for XORed data.
|
||||
// length) tuples. Hashes order in response corresponds to the ranges order in
|
||||
// the request. Note that hash is calculated for XORed data.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS): \
|
||||
|
@ -353,12 +353,12 @@ message HeadRequest {
|
|||
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
|
||||
}
|
||||
|
||||
// Tuple of full object header and signature of `ObjectID`. \
|
||||
// Tuple of a full object header and signature of an `ObjectID`. \
|
||||
// Signed `ObjectID` is present to verify full header's authenticity through the
|
||||
// following steps:
|
||||
//
|
||||
// 1. Calculate `SHA-256` of marshalled `Header` structure
|
||||
// 2. Check if the resulting hash matched `ObjectID`
|
||||
// 1. Calculate `SHA-256` of the marshalled `Header` structure
|
||||
// 2. Check if the resulting hash matches `ObjectID`
|
||||
// 3. Check if `ObjectID` signature in `signature` field is correct
|
||||
message HeaderWithSignature {
|
||||
// Full object header
|
||||
|
@ -407,13 +407,13 @@ message SearchRequest {
|
|||
|
||||
// Version of the Query Language used
|
||||
uint32 version = 2;
|
||||
// Filter structure checks if object header field or attribute content
|
||||
// Filter structure checks if the object header field or the attribute content
|
||||
// matches a value.
|
||||
//
|
||||
// If no filters set, search request will return all objects of the
|
||||
// If no filters are set, search request will return all objects of the
|
||||
// container, including Regular object, Tombstones and Storage Group
|
||||
// objects. Most human users expect to get only object they can directly
|
||||
// work with. In that case the `$Object:ROOT` filter should be used.
|
||||
// work with. In that case, `$Object:ROOT` filter should be used.
|
||||
//
|
||||
// By default `key` field refers to the corresponding object's `Attribute`.
|
||||
// Some Object's header fields can also be accessed by adding `$Object:`
|
||||
|
@ -446,10 +446,10 @@ message SearchRequest {
|
|||
// properties:
|
||||
//
|
||||
// * $Object:ROOT \
|
||||
// Returns only `REGULAR` type objects that are not split or are the top
|
||||
// Returns only `REGULAR` type objects that are not split or that are the top
|
||||
// level root objects in a split hierarchy. This includes objects not
|
||||
// present physically, like large objects split into smaller objects
|
||||
// without separate top-level root object. Other type objects like
|
||||
// without a separate top-level root object. Objects of other types like
|
||||
// StorageGroups and Tombstones will not be shown. This filter may be
|
||||
// useful for listing objects like `ls` command of some virtual file
|
||||
// system. This filter is activated if the `key` exists, disregarding the
|
||||
|
|
|
@ -9,7 +9,7 @@ import "refs/types.proto";
|
|||
import "session/types.proto";
|
||||
|
||||
// Type of the object payload content. Only `REGULAR` type objects can be split,
|
||||
// hence `TOMBSTONE`, `STORAGE_GROUP` and `LOCK` payload is limited by maximal
|
||||
// hence `TOMBSTONE`, `STORAGE_GROUP` and `LOCK` payload is limited by the maximum
|
||||
// object size.
|
||||
//
|
||||
// String presentation of object type is the same as definition:
|
||||
|
@ -51,7 +51,7 @@ enum MatchType {
|
|||
|
||||
// Short header fields
|
||||
message ShortHeader {
|
||||
// Object format version. Effectively the version of API library used to
|
||||
// Object format version. Effectively, the version of API library used to
|
||||
// create particular object.
|
||||
neo.fs.v2.refs.Version version = 1 [json_name = "version"];
|
||||
|
||||
|
@ -77,7 +77,7 @@ message ShortHeader {
|
|||
|
||||
// Object Header
|
||||
message Header {
|
||||
// Object format version. Effectively the version of API library used to
|
||||
// Object format version. Effectively, the version of API library used to
|
||||
// create particular object
|
||||
neo.fs.v2.refs.Version version = 1 [json_name = "version"];
|
||||
|
||||
|
@ -107,10 +107,10 @@ message Header {
|
|||
// integrity and authenticity out of Request scope.
|
||||
neo.fs.v2.session.SessionToken session_token = 9 [json_name = "sessionToken"];
|
||||
|
||||
// `Attribute` is a user-defined Key-Value metadata pair attached to the
|
||||
// `Attribute` is a user-defined Key-Value metadata pair attached to an
|
||||
// object.
|
||||
//
|
||||
// Key name must be a object-unique valid UTF-8 string. Value can't be empty.
|
||||
// Key name must be an object-unique valid UTF-8 string. Value can't be empty.
|
||||
// Objects with duplicated attribute names or attributes with empty values
|
||||
// will be considered invalid.
|
||||
//
|
||||
|
@ -141,7 +141,7 @@ message Header {
|
|||
// MIME Content Type of object's payload
|
||||
//
|
||||
// For detailed description of each well-known attribute please see the
|
||||
// corresponding section in NeoFS Technical specification.
|
||||
// corresponding section in NeoFS Technical Specification.
|
||||
message Attribute {
|
||||
// string key to the object attribute
|
||||
string key = 1 [json_name = "key"];
|
||||
|
@ -182,8 +182,8 @@ message Header {
|
|||
}
|
||||
|
||||
// Object structure. Object is immutable and content-addressed. It means
|
||||
// `ObjectID` will change if header or payload changes. It's calculated as a
|
||||
// hash of header field, which contains hash of object's payload.
|
||||
// `ObjectID` will change if the header or the payload changes. It's calculated as a
|
||||
// hash of header field which contains hash of the object's payload.
|
||||
//
|
||||
// For non-regular object types payload format depends on object type specified
|
||||
// in the header.
|
||||
|
@ -201,20 +201,20 @@ message Object {
|
|||
bytes payload = 4 [json_name = "payload"];
|
||||
}
|
||||
|
||||
// Meta information of split hierarchy for object assembly. With last part
|
||||
// one can traverse linked list of split hierarchy back to first part and
|
||||
// assemble original object. With linking object one can assembly object
|
||||
// straight away from the object parts.
|
||||
// Meta information of split hierarchy for object assembly. With the last part
|
||||
// one can traverse linked list of split hierarchy back to the first part and
|
||||
// assemble the original object. With a linking object one can assemble an object
|
||||
// right from the object parts.
|
||||
message SplitInfo {
|
||||
// 16 byte UUID used to identify the split object hierarchy parts.
|
||||
bytes split_id = 1;
|
||||
|
||||
// Identifier of the last object in split hierarchy parts. It contains
|
||||
// split header with original object header.
|
||||
// The identifier of the last object in split hierarchy parts. It contains
|
||||
// split header with the original object header.
|
||||
neo.fs.v2.refs.ObjectID last_part = 2;
|
||||
|
||||
// Identifier of linking object for split hierarchy parts. It contains
|
||||
// split header with original object header and sorted list of
|
||||
// The identifier of a linking object for split hierarchy parts. It contains
|
||||
// split header with the original object header and a sorted list of
|
||||
// object parts.
|
||||
neo.fs.v2.refs.ObjectID link = 3;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ option csharp_namespace = "Neo.FileStorage.API.Refs";
|
|||
|
||||
// Objects in NeoFS are addressed by their ContainerID and ObjectID.
|
||||
//
|
||||
// String presentation of `Address` is the concatenation of string encoded
|
||||
// String presentation of `Address` is a concatenation of string encoded
|
||||
// `ContainerID` and `ObjectID` delimited by '/' character.
|
||||
message Address {
|
||||
// Container identifier
|
||||
|
@ -17,17 +17,17 @@ message Address {
|
|||
}
|
||||
|
||||
// NeoFS Object unique identifier. Objects are immutable and content-addressed.
|
||||
// It means `ObjectID` will change if `header` or `payload` changes.
|
||||
// It means `ObjectID` will change if the `header` or the `payload` changes.
|
||||
//
|
||||
// `ObjectID` is a 32 byte long
|
||||
// [SHA256](https://csrc.nist.gov/publications/detail/fips/180/4/final) hash of
|
||||
// object's `header` field, which, in it's turn, contains hash of object's
|
||||
// the object's `header` field, which, in it's turn, contains the hash of the object's
|
||||
// payload.
|
||||
//
|
||||
// String presentation is
|
||||
// String presentation is a
|
||||
// [base58](https://tools.ietf.org/html/draft-msporny-base58-02) encoded string.
|
||||
//
|
||||
// JSON value will be the data encoded as a string using standard base64
|
||||
// JSON value will be data encoded as a string using standard base64
|
||||
// encoding with paddings. Either
|
||||
// [standard](https://tools.ietf.org/html/rfc4648#section-4) or
|
||||
// [URL-safe](https://tools.ietf.org/html/rfc4648#section-5) base64 encoding
|
||||
|
@ -44,10 +44,10 @@ message ObjectID {
|
|||
// [SHA256](https://csrc.nist.gov/publications/detail/fips/180/4/final) hash of
|
||||
// stable-marshalled container message.
|
||||
//
|
||||
// String presentation is
|
||||
// String presentation is a
|
||||
// [base58](https://tools.ietf.org/html/draft-msporny-base58-02) encoded string.
|
||||
//
|
||||
// JSON value will be the data encoded as a string using standard base64
|
||||
// JSON value will be data encoded as a string using standard base64
|
||||
// encoding with paddings. Either
|
||||
// [standard](https://tools.ietf.org/html/rfc4648#section-4) or
|
||||
// [URL-safe](https://tools.ietf.org/html/rfc4648#section-5) base64 encoding
|
||||
|
@ -64,10 +64,10 @@ message ContainerID {
|
|||
// `OwnerID` is a 25 bytes sequence starting with Neo version prefix byte
|
||||
// followed by 20 bytes of ScrptHash and 4 bytes of checksum.
|
||||
//
|
||||
// String presentation is [Base58
|
||||
// String presentation is a [Base58
|
||||
// Check](https://en.bitcoin.it/wiki/Base58Check_encoding) Encoded string.
|
||||
//
|
||||
// JSON value will be the data encoded as a string using standard base64
|
||||
// JSON value will be data encoded as a string using standard base64
|
||||
// encoding with paddings. Either
|
||||
// [standard](https://tools.ietf.org/html/rfc4648#section-4) or
|
||||
// [URL-safe](https://tools.ietf.org/html/rfc4648#section-5) base64 encoding
|
||||
|
@ -81,7 +81,7 @@ message OwnerID {
|
|||
//
|
||||
// String representation of a value is base-10 integer.
|
||||
//
|
||||
// JSON representation is an object containing single `value` number field.
|
||||
// JSON representation is an object containing a single `value` number field.
|
||||
message SubnetID {
|
||||
// 4-byte integer subnetwork identifier.
|
||||
fixed32 value = 1 [json_name = "value"];
|
||||
|
@ -90,7 +90,7 @@ message SubnetID {
|
|||
// API version used by a node.
|
||||
//
|
||||
// String presentation is a Semantic Versioning 2.0.0 compatible version string
|
||||
// with 'v' prefix. I.e. `vX.Y`, where `X` - major number, `Y` - minor number.
|
||||
// with 'v' prefix. i.e. `vX.Y`, where `X` is the major number, `Y` is the minor number.
|
||||
message Version {
|
||||
// Major API version
|
||||
uint32 major = 1 [json_name = "major"];
|
||||
|
@ -139,7 +139,7 @@ enum ChecksumType {
|
|||
}
|
||||
|
||||
// Checksum message.
|
||||
// Depending on checksum algorithm type the string presentation may vary:
|
||||
// Depending on checksum algorithm type, the string presentation may vary:
|
||||
//
|
||||
// * TZ \
|
||||
// Hex encoded string without `0x` prefix
|
||||
|
|
|
@ -22,7 +22,7 @@ service ReputationService {
|
|||
// - Common failures (SECTION_FAILURE_COMMON).
|
||||
rpc AnnounceLocalTrust (AnnounceLocalTrustRequest) returns (AnnounceLocalTrustResponse);
|
||||
|
||||
// Announces the intermediate result of the iterative algorithm for
|
||||
// Announce the intermediate result of the iterative algorithm for
|
||||
// calculating the global reputation of the node in NeoFS network.
|
||||
//
|
||||
// Statuses:
|
||||
|
@ -41,7 +41,7 @@ message AnnounceLocalTrustRequest {
|
|||
|
||||
// List of normalized local trust values to other NeoFS nodes. The value
|
||||
// is calculated according to EigenTrust++ algorithm and must be a
|
||||
// floating point number in the [0;1] range.
|
||||
// floating point number in [0;1] range.
|
||||
repeated Trust trusts = 2;
|
||||
}
|
||||
|
||||
|
@ -58,11 +58,11 @@ message AnnounceLocalTrustRequest {
|
|||
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
|
||||
}
|
||||
|
||||
// Node's local trust information announce response.
|
||||
// Node's local trust information announcement response.
|
||||
message AnnounceLocalTrustResponse {
|
||||
// Response to the node's local trust information announce has an empty body
|
||||
// Response to the node's local trust information announcement has an empty body
|
||||
// because the trust exchange operation is asynchronous. If Trust information
|
||||
// will not pass sanity checks it is silently ignored.
|
||||
// does not pass sanity checks, it is silently ignored.
|
||||
message Body {
|
||||
}
|
||||
|
||||
|
@ -106,11 +106,11 @@ message AnnounceIntermediateResultRequest {
|
|||
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
|
||||
}
|
||||
|
||||
// Intermediate global trust information announce response.
|
||||
// Intermediate global trust information announcement response.
|
||||
message AnnounceIntermediateResultResponse {
|
||||
// Response to the node's intermediate global trust information announce has
|
||||
// Response to the node's intermediate global trust information announcement has
|
||||
// an empty body because the trust exchange operation is asynchronous. If
|
||||
// Trust information will not pass sanity checks it is silently ignored.
|
||||
// Trust information does not pass sanity checks, it is silently ignored.
|
||||
message Body {
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ option csharp_namespace = "Neo.FileStorage.API.Reputation";
|
|||
|
||||
import "refs/types.proto";
|
||||
|
||||
// NeoFS unique peer identifier is 33 byte long compressed public key of the
|
||||
// NeoFS unique peer identifier is a 33 byte long compressed public key of the
|
||||
// node, the same as the one stored in the network map.
|
||||
//
|
||||
// String presentation is
|
||||
// String presentation is a
|
||||
// [base58](https://tools.ietf.org/html/draft-msporny-base58-02) encoded string.
|
||||
//
|
||||
// JSON value will be the data encoded as a string using standard base64
|
||||
// JSON value will be data encoded as a string using standard base64
|
||||
// encoding with paddings. Either
|
||||
// [standard](https://tools.ietf.org/html/rfc4648#section-4) or
|
||||
// [URL-safe](https://tools.ietf.org/html/rfc4648#section-5) base64 encoding
|
||||
|
@ -43,7 +43,7 @@ message PeerToPeerTrust {
|
|||
|
||||
// Global trust level to NeoFS node.
|
||||
message GlobalTrust {
|
||||
// Message format version. Effectively the version of API library used to create
|
||||
// Message format version. Effectively, the version of API library used to create
|
||||
// the message.
|
||||
neo.fs.v2.refs.Version version = 1 [json_name = "version"];
|
||||
// Message body structure.
|
||||
|
|
|
@ -13,7 +13,7 @@ import "session/types.proto";
|
|||
// attached in requests for further verification. Please see corresponding
|
||||
// section of NeoFS Technical Specification for details.
|
||||
service SessionService {
|
||||
// Opens a new session between two peers.
|
||||
// Open a new session between two peers.
|
||||
//
|
||||
// Statuses:
|
||||
// - **OK** (0, SECTION_SUCCESS):
|
||||
|
@ -31,7 +31,7 @@ message CreateRequest {
|
|||
// Session expiration `Epoch`
|
||||
uint64 expiration = 2;
|
||||
}
|
||||
// Body of create session token request message.
|
||||
// Body of a create session token request message.
|
||||
Body body = 1;
|
||||
|
||||
// Carries request meta information. Header data is used only to regulate
|
||||
|
|
|
@ -117,10 +117,10 @@ message SessionToken {
|
|||
neo.fs.v2.refs.Signature signature = 2 [json_name = "signature"];
|
||||
}
|
||||
|
||||
// Extended headers for Request/Response. May contain any user-defined headers
|
||||
// Extended headers for Request/Response. They may contain any user-defined headers
|
||||
// to be interpreted on application level.
|
||||
//
|
||||
// Key name must be unique valid UTF-8 string. Value can't be empty. Requests or
|
||||
// Key name must be a unique valid UTF-8 string. Value can't be empty. Requests or
|
||||
// Responses with duplicated header names or headers with empty values will be
|
||||
// considered invalid.
|
||||
//
|
||||
|
@ -133,9 +133,9 @@ message SessionToken {
|
|||
// current epoch only will be used.
|
||||
// * __NEOFS__NETMAP_LOOKUP_DEPTH \
|
||||
// If object can't be found using current epoch's netmap, this header limits
|
||||
// how many past epochs back the node can lookup. The `value` is string
|
||||
// encoded `uint64` in decimal presentation. If set to '0' or not set, the
|
||||
// current epoch only will be used.
|
||||
// how many past epochs the node can look up through. The `value` is string
|
||||
// encoded `uint64` in decimal presentation. If set to '0' or not set, only the
|
||||
// current epoch will be used.
|
||||
message XHeader {
|
||||
// Key of the X-Header
|
||||
string key = 1 [json_name = "key"];
|
||||
|
@ -194,9 +194,9 @@ message ResponseMetaHeader {
|
|||
neo.fs.v2.status.Status status = 6 [json_name = "status"];
|
||||
}
|
||||
|
||||
// Verification info for request signed by all intermediate nodes.
|
||||
// Verification info for the request signed by all intermediate nodes.
|
||||
message RequestVerificationHeader {
|
||||
// Request Body signature. Should be generated once by request initiator.
|
||||
// Request Body signature. Should be generated once by the request initiator.
|
||||
neo.fs.v2.refs.Signature body_signature = 1 [json_name = "bodySignature"];
|
||||
// Request Meta signature is added and signed by each intermediate node
|
||||
neo.fs.v2.refs.Signature meta_signature = 2 [json_name = "metaSignature"];
|
||||
|
@ -207,9 +207,9 @@ message RequestVerificationHeader {
|
|||
RequestVerificationHeader origin = 4 [json_name = "origin"];
|
||||
}
|
||||
|
||||
// Verification info for response signed by all intermediate nodes
|
||||
// Verification info for the response signed by all intermediate nodes
|
||||
message ResponseVerificationHeader {
|
||||
// Response Body signature. Should be generated once by answering node.
|
||||
// Response Body signature. Should be generated once by an answering node.
|
||||
neo.fs.v2.refs.Signature body_signature = 1 [json_name = "bodySignature"];
|
||||
// Response Meta signature is added and signed by each intermediate node
|
||||
neo.fs.v2.refs.Signature meta_signature = 2 [json_name = "metaSignature"];
|
||||
|
|
|
@ -23,12 +23,12 @@ option csharp_namespace = "Neo.FileStorage.API.Status";
|
|||
//
|
||||
// All outcomes are divided into successful and failed, which corresponds
|
||||
// to the success or failure of the operation. The definition of success
|
||||
// follows from the semantics of RPC and the description of its purpose.
|
||||
// The server must not attach code that is the opposite of outcome type.
|
||||
// follows the semantics of RPC and the description of its purpose.
|
||||
// The server must not attach code that is the opposite of the outcome type.
|
||||
//
|
||||
// See the set of return codes in the description for calls.
|
||||
//
|
||||
// Each status can carry developer-facing error message. It should be human
|
||||
// Each status can carry a developer-facing error message. It should be a human
|
||||
// readable text in English. The server should not transmit (and the client
|
||||
// should not expect) useful information in the message. Field `details`
|
||||
// should make the return more detailed.
|
||||
|
|
|
@ -8,8 +8,8 @@ option csharp_namespace = "Neo.FileStorage.API.StorageGroup";
|
|||
import "refs/types.proto";
|
||||
|
||||
// StorageGroup keeps verification information for Data Audit sessions. Objects
|
||||
// that require payed storage guaranties are gathered in `StorageGroups` with
|
||||
// additional information used for proof of storage. `StorageGroup` only
|
||||
// that require paid storage guarantees are gathered in `StorageGroups` with
|
||||
// additional information used for the proof of storage. `StorageGroup` only
|
||||
// contains objects from the same container.
|
||||
message StorageGroup {
|
||||
// Total size of the payloads of objects in the storage group
|
||||
|
|
|
@ -7,17 +7,17 @@ option csharp_namespace = "Neo.FileStorage.API.Tombstone";
|
|||
|
||||
import "refs/types.proto";
|
||||
|
||||
// Tombstone keeps record of deleted objects for few epochs until they are
|
||||
// Tombstone keeps record of deleted objects for a few epochs until they are
|
||||
// purged from the NeoFS network.
|
||||
message Tombstone {
|
||||
// Last NeoFS epoch number of the tombstone lifetime. It's set by tombstone
|
||||
// creator depending on current NeoFS network settings. Tombstone object
|
||||
// Last NeoFS epoch number of the tombstone lifetime. It's set by the tombstone
|
||||
// creator depending on the current NeoFS network settings. A tombstone object
|
||||
// must have the same expiration epoch value in `__NEOFS__EXPIRATION_EPOCH`
|
||||
// attribute. Otherwise tombstone will be rejected by storage node.
|
||||
// attribute. Otherwise, the tombstone will be rejected by a storage node.
|
||||
uint64 expiration_epoch = 1 [json_name = "expirationEpoch"];
|
||||
|
||||
// 16 byte UUID used to identify the split object hierarchy parts. Must be
|
||||
// unique inside container. All objects participating in the split must
|
||||
// unique inside a container. All objects participating in the split must
|
||||
// have the same `split_id` value.
|
||||
bytes split_id = 2 [json_name = "splitID"];
|
||||
|
||||
|
|
Loading…
Reference in a new issue