[#1] Define SDK structure, add operations with container and object

Signed-off-by: Ori Bruk <o.bruk@yadro.com>
This commit is contained in:
Ori Bruk 2024-06-11 17:34:39 +03:00
parent 04bf8249c2
commit 2481774545
80 changed files with 6315 additions and 0 deletions

40
.gitignore vendored Normal file
View file

@ -0,0 +1,40 @@
### Maven ###
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

156
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,156 @@
# Contribution guide
First, thank you for contributing! We love and encourage pull requests from
everyone. Please follow the guidelines:
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-sdk-java/issues) and
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-sdk-java/pulls) for existing
discussions.
- Open an issue first, to discuss a new feature or enhancement.
- Write tests and make sure the test suite passes locally and on CI.
- Open a pull request and reference the relevant issue(s).
- Make sure your commits are logically separated and have good comments
explaining the details of your change.
- After receiving a feedback, amend your commits or add new ones as
appropriate.
- **Have fun!**
## Development Workflow
Start by forking the `frostfs-sdk-java` repository, make changes in a branch and then
send a pull request. We encourage pull requests to discuss code changes. Here
are the steps in details:
### Set up your git repository
Fork [FrostFS S3 Gateway
upstream](https://git.frostfs.info/repo/fork/346) source repository
to your own personal repository. Copy the URL of your fork (you will need it for
the `git clone` command below).
```sh
$ git clone https://git.frostfs.info/<username>/frostfs-sdk-java.git
```
### Set up git remote as ``upstream``
```sh
$ cd frostfs-sdk-java
$ git remote add upstream https://git.frostfs.info/TrueCloudLab/frostfs-sdk-java.git
$ git fetch upstream
$ 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 a branch in
`<type>/<Issue>-<changes_topic>` format.
```
$ git checkout -b feature/123-something_awesome
```
### Test your changes
After your code changes, make sure
- To add test cases for the new code.
- To run `mvn clean verify`
- To squash your commits into a single commit or a series of logically separated
commits with `git rebase -i`. It's okay to force update your pull request.
- To run `mvn clean package` successfully.
### Commit changes
After verification, commit your changes. There is a [great
post](https://chris.beams.io/posts/git-commit/) on how to write useful commit
messages. Try following this template:
```
[#Issue] <component> Summary
Description
<Macros>
<Sign-Off>
```
```
$ git commit -ams '[#123] Add some feature'
```
### Push to the branch
Push your locally committed changes to the remote origin (your fork)
```
$ git push origin feature/123-something_awesome
```
### Create a Pull Request
Pull requests can be created via Forgejo. Refer to [this
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for
detailed steps on how to create a pull request. After a Pull Request gets peer
reviewed and approved, it will be merged.
## DCO Sign off
All authors to the project retain copyright to their work. However, to ensure
that they are only submitting work that they have rights to, we require
everyone to acknowledge this by signing their work.
Any copyright notices in this repository should specify the authors as "the
contributors".
To sign your work, just add a line like this at the end of your commit message:
```
Signed-off-by: Samii Sakisaka <samii@frostfs.info>
```
This can be easily done with the `--signoff` option to `git commit`.
By doing this you state that you can certify the following (from [The Developer
Certificate of Origin](https://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```

View file

@ -1,3 +1,94 @@
# frostfs-sdk-java
Java implementation of FrostFS SDK
## Prerequisites
### Get the key for your wallet
1. Get the address
```bash
cat <path_to_your_wallet> | jq .accounts[0].address | tr -d '"'
```
2. Get the key
```bash
neo-go wallet export -w <path_to_your_wallet> -d <address_from_p1>
```
## Example usage
### Container
```java
import info.FrostFS.sdk.enums.BasicAcl;
import info.FrostFS.sdk.jdo.Container;
import info.FrostFS.sdk.jdo.netmap.PlacementPolicy;
import info.FrostFS.sdk.jdo.netmap.Replica;
import info.FrostFS.sdk.services.impl.FrostFSClient;
public class ContainerExample {
public void example() {
Client client = new Client( < your_key >);
GrpcClient grpcClient = new GrpcClient( < your_host >);
FrostFSClient frostFSClient = new FrostFSClient(grpcClient, client);
// Create container
var placementPolicy = new PlacementPolicy(true, new Replica[]{new Replica(1)});
var containerId = frostFSClient.createContainerAsync(new Container(BasicAcl.PUBLIC_RW, placementPolicy));
// Get container
var container = frostFSClient.getContainerAsync(containerId);
// List containers
var containerIds = frostFSClient.listContainersAsync();
// Delete container
frostFSClient.deleteContainerAsync(containerId);
}
}
```
### Object
```java
import info.FrostFS.sdk.enums.ObjectType;
import info.FrostFS.sdk.jdo.ContainerId;
import info.FrostFS.sdk.jdo.ObjectAttribute;
import info.FrostFS.sdk.jdo.ObjectFilter;
import info.FrostFS.sdk.jdo.ObjectHeader;
import info.FrostFS.sdk.services.impl.FrostFSClient;
import java.io.FileInputStream;
import java.io.IOException;
public class ObjectExample {
public void example() {
Client client = new Client( < your_key >);
GrpcClient grpcClient = new GrpcClient( < your_host >);
FrostFSClient frostFSClient = new FrostFSClient(grpcClient, client);
// Put object
info.FrostFS.sdk.jdo.ObjectId objectId;
try (FileInputStream fis = new FileInputStream("cat.jpg")) {
var cat = new ObjectHeader(
containerId, ObjectType.REGULAR, new ObjectAttribute[]{new ObjectAttribute("Filename", "cat.jpg")}
);
objectId = frostFSClient.putObjectAsync(cat, fis);
} catch (IOException e) {
throw new RuntimeException(e);
}
// Get object
var obj = frostFSClient.getObjectAsync(containerId, objectId);
// Get object header
var objectHeader = frostFSClient.getObjectHeadAsync(containerId, objectId);
// Search regular objects
var objectIds = frostFSClient.searchObjectsAsync(containerId, ObjectFilter.RootFilter());
}
}
```

48
client/pom.xml Normal file
View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>FrostFS-sdk-java</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>client</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>cryptography</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>protosV2</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>modelsV2</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,36 @@
package info.FrostFS.sdk;
import info.FrostFS.sdk.jdo.OwnerId;
import info.FrostFS.sdk.jdo.Version;
import static info.FrostFS.sdk.KeyExtension.getPrivateKeyFromWIF;
import static info.FrostFS.sdk.KeyExtension.loadPublicKey;
public class Client {
private final OwnerId ownerId;
private final Version version = new Version(2, 13);
private final byte[] privateKey;
private final byte[] publicKey;
public Client(String key) {
this.privateKey = getPrivateKeyFromWIF(key);
this.publicKey = loadPublicKey(privateKey);
this.ownerId = OwnerId.fromKey(publicKey);
}
public OwnerId getOwnerId() {
return ownerId;
}
public Version getVersion() {
return version;
}
public byte[] getPrivateKey() {
return privateKey;
}
public byte[] getPublicKey() {
return publicKey;
}
}

View file

@ -0,0 +1,89 @@
package info.FrostFS.sdk;
import frostFS.container.ContainerServiceGrpc;
import frostFS.netmap.NetmapServiceGrpc;
import frostFS.object.ObjectServiceGrpc;
import frostFS.session.SessionServiceGrpc;
import io.grpc.Channel;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLException;
import java.net.URI;
import java.net.URISyntaxException;
public class GrpcClient {
private static final Logger log = LoggerFactory.getLogger(GrpcClient.class);
private final ContainerServiceGrpc.ContainerServiceBlockingStub containerServiceBlockingClient;
private final NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceBlockingClient;
private final ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceBlockingClient;
private final ObjectServiceGrpc.ObjectServiceStub objectServiceClient;
private final SessionServiceGrpc.SessionServiceBlockingStub sessionServiceBlockingClient;
public GrpcClient(String host) {
Channel channel = initGrpcChannel(host);
this.containerServiceBlockingClient = ContainerServiceGrpc.newBlockingStub(channel);
this.netmapServiceBlockingClient = NetmapServiceGrpc.newBlockingStub(channel);
this.objectServiceBlockingClient = ObjectServiceGrpc.newBlockingStub(channel);
this.objectServiceClient = ObjectServiceGrpc.newStub(channel);
this.sessionServiceBlockingClient = SessionServiceGrpc.newBlockingStub(channel);
}
public static Channel initGrpcChannel(String host) {
URI uri;
try {
uri = new URI(host);
} catch (URISyntaxException exp) {
var message = String.format("Host %s has invalid format. Error: %s", host, exp.getMessage());
log.error(message);
throw new IllegalArgumentException(message);
}
var channelBuilder = NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort())
.usePlaintext();
switch (uri.getScheme()) {
case "https":
try {
channelBuilder.sslContext(
GrpcSslContexts.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build()
);
} catch (SSLException e) {
throw new RuntimeException(e);
}
break;
case "http":
break;
default:
var message = String.format("Host %s has invalid URI scheme: %s", host, uri.getScheme());
log.error(message);
throw new IllegalArgumentException(message);
}
return channelBuilder.build();
}
public ContainerServiceGrpc.ContainerServiceBlockingStub getContainerServiceBlockingClient() {
return containerServiceBlockingClient;
}
public NetmapServiceGrpc.NetmapServiceBlockingStub getNetmapServiceBlockingClient() {
return netmapServiceBlockingClient;
}
public ObjectServiceGrpc.ObjectServiceBlockingStub getObjectServiceBlockingClient() {
return objectServiceBlockingClient;
}
public ObjectServiceGrpc.ObjectServiceStub getObjectServiceClient() {
return objectServiceClient;
}
public SessionServiceGrpc.SessionServiceBlockingStub getSessionServiceBlockingClient() {
return sessionServiceBlockingClient;
}
}

View file

@ -0,0 +1,50 @@
package info.FrostFS.sdk;
import com.google.protobuf.AbstractMessage;
import frostFS.session.Types;
import info.FrostFS.sdk.jdo.MetaHeader;
import info.FrostFS.sdk.mappers.MetaHeaderMapper;
import static info.FrostFS.sdk.RequestSigner.signMessagePart;
import static java.util.Objects.isNull;
public class RequestConstructor {
public static void addMetaHeader(AbstractMessage.Builder request) {
addMetaHeader(request, null);
}
public static void addMetaHeader(AbstractMessage.Builder request, Types.RequestMetaHeader metaHeader) {
if (isNull(metaHeader) || metaHeader.getSerializedSize() == 0) {
metaHeader = MetaHeaderMapper.toGrpcMessage(MetaHeader.getDefault());
var field = request.getDescriptorForType().findFieldByName("meta_header");
request.setField(field, metaHeader);
}
}
public static void addObjectSessionToken(AbstractMessage.Builder request,
Types.SessionToken sessionToken,
frostFS.refs.Types.ContainerID cid,
frostFS.refs.Types.ObjectID oid,
Types.ObjectSessionContext.Verb verb,
byte[] publicKey, byte[] privateKey) {
var headerField = request.getDescriptorForType().findFieldByName("meta_header");
var header = (Types.RequestMetaHeader) request.getField(headerField);
if (header.getSessionToken().getSerializedSize() > 0) {
return;
}
var ctx = Types.ObjectSessionContext.newBuilder()
.setTarget(Types.ObjectSessionContext.Target.newBuilder().setContainer(cid).addObjects(oid).build())
.setVerb(verb)
.build();
var body = sessionToken.getBody().toBuilder().setObject(ctx).build();
sessionToken = sessionToken.toBuilder()
.setSignature(signMessagePart(publicKey, privateKey, body))
.setBody(body)
.build();
request.setField(headerField, header.toBuilder().setSessionToken(sessionToken).build());
}
}

View file

@ -0,0 +1,114 @@
package info.FrostFS.sdk;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ByteString;
import frostFS.session.Types;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import java.math.BigInteger;
import java.security.Signature;
import static info.FrostFS.sdk.KeyExtension.loadPrivateKey;
import static org.bouncycastle.crypto.util.DigestFactory.createSHA256;
import static org.bouncycastle.util.BigIntegers.asUnsignedByteArray;
public class RequestSigner {
public static final int RFC6979_SIGNATURE_SIZE = 64;
public static byte[] signData(byte[] privateKey, byte[] data) {
var hash = new byte[65];
hash[0] = 0x04;
try {
Signature signature = Signature.getInstance("NONEwithECDSAinP1363Format");
signature.initSign(loadPrivateKey(privateKey));
signature.update(DigestUtils.sha512(data));
byte[] sig = signature.sign();
System.arraycopy(sig, 0, hash, 1, sig.length);
} catch (Exception exp) {
throw new RuntimeException(exp);
}
return hash;
}
public static byte[] signRFC6979(byte[] privateKey, byte[] data) {
var digest = createSHA256();
var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1);
var ecParameters = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN());
var ecPrivateKey = new ECPrivateKeyParameters(new BigInteger(1, privateKey), ecParameters);
var signer = new ECDSASigner(new HMacDSAKCalculator(digest));
var hash = new byte[digest.getDigestSize()];
digest.update(data, 0, data.length);
digest.doFinal(hash, 0);
signer.init(true, ecPrivateKey);
var rs = signer.generateSignature(hash);
var rBytes = asUnsignedByteArray(rs[0]);
var sBytes = asUnsignedByteArray(rs[1]);
var signature = new byte[RFC6979_SIGNATURE_SIZE];
var index = RFC6979_SIGNATURE_SIZE / 2 - rBytes.length;
System.arraycopy(rBytes, 0, signature, index, rBytes.length);
index = RFC6979_SIGNATURE_SIZE - sBytes.length;
System.arraycopy(sBytes, 0, signature, index, sBytes.length);
return signature;
}
public static frostFS.refs.Types.SignatureRFC6979 signRFC6979(byte[] publicKey, byte[] privateKey, AbstractMessage message) {
return frostFS.refs.Types.SignatureRFC6979.newBuilder()
.setKey(ByteString.copyFrom(publicKey))
.setSign(ByteString.copyFrom(signRFC6979(privateKey, message.toByteArray())))
.build();
}
public static frostFS.refs.Types.SignatureRFC6979 signRFC6979(byte[] publicKey, byte[] privateKey, ByteString data) {
return frostFS.refs.Types.SignatureRFC6979.newBuilder()
.setKey(ByteString.copyFrom(publicKey))
.setSign(ByteString.copyFrom(signRFC6979(privateKey, data.toByteArray())))
.build();
}
public static frostFS.refs.Types.Signature signMessagePart(byte[] publicKey, byte[] privateKey, AbstractMessage data) {
var data2Sign = data.getSerializedSize() == 0 ? new byte[]{} : data.toByteArray();
return frostFS.refs.Types.Signature.newBuilder()
.setKey(ByteString.copyFrom(publicKey))
.setSign(ByteString.copyFrom(signData(privateKey, data2Sign)))
.build();
}
public static void sign(AbstractMessage.Builder request, byte[] publicKey, byte[] privateKey) {
var meta = (AbstractMessage) request.getField(request.getDescriptorForType().findFieldByName("meta_header"));
var body = (AbstractMessage) request.getField(request.getDescriptorForType().findFieldByName("body"));
var verify = (AbstractMessage) request.getField(request.getDescriptorForType().findFieldByName("verify_header"));
AbstractMessage.Builder verifyBuilder;
if (verify instanceof Types.RequestVerificationHeader) {
verifyBuilder = Types.RequestVerificationHeader.newBuilder();
} else if (verify instanceof Types.ResponseVerificationHeader) {
verifyBuilder = Types.ResponseVerificationHeader.newBuilder();
} else {
throw new IllegalArgumentException("Unsopported message type");
}
var verifyOrigin = (AbstractMessage) verify.getField(verify.getDescriptorForType().findFieldByName("origin"));
if (verifyOrigin.getSerializedSize() == 0) {
verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("body_signature"), signMessagePart(publicKey, privateKey, body));
} else {
verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("origin"), verifyOrigin);
}
verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("meta_signature"), signMessagePart(publicKey, privateKey, meta));
verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("origin_signature"), signMessagePart(publicKey, privateKey, verifyOrigin));
request.setField(request.getDescriptorForType().findFieldByName("verify_header"), verifyBuilder.build());
}
}

View file

@ -0,0 +1,116 @@
package info.FrostFS.sdk;
import com.google.protobuf.AbstractMessage;
import frostFS.refs.Types;
import info.FrostFS.sdk.mappers.StatusMapper;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Arrays;
import static info.FrostFS.sdk.KeyExtension.getPublicKeyByPublic;
import static java.util.Objects.isNull;
import static org.bouncycastle.crypto.util.DigestFactory.createSHA256;
import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray;
public class Verifier {
public static final int RFC6979_SIGNATURE_SIZE = 64;
public static boolean verifyRFC6979(Types.SignatureRFC6979 signature, AbstractMessage message) {
return verifyRFC6979(signature.getKey().toByteArray(), message.toByteArray(), signature.getSign().toByteArray());
}
public static boolean verifyRFC6979(byte[] publicKey, byte[] data, byte[] sig) {
if (isNull(publicKey) || isNull(data) || isNull(sig)) return false;
var rs = decodeSignature(sig);
var digest = createSHA256();
var signer = new ECDSASigner(new HMacDSAKCalculator(digest));
var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1);
var ecParameters = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN());
var ecPublicKey = new ECPublicKeyParameters(secp256R1.getCurve().decodePoint(publicKey), ecParameters);
var hash = new byte[digest.getDigestSize()];
digest.update(data, 0, data.length);
digest.doFinal(hash, 0);
signer.init(false, ecPublicKey);
return signer.verifySignature(hash, rs[0], rs[1]);
}
private static BigInteger[] decodeSignature(byte[] sig) {
if (sig.length != RFC6979_SIGNATURE_SIZE) {
throw new IllegalArgumentException(
String.format("Wrong signature size. Expected length=%s, actual=%s",
RFC6979_SIGNATURE_SIZE, sig.length)
);
}
var rs = new BigInteger[2];
rs[0] = fromUnsignedByteArray(Arrays.copyOfRange(sig, 0, (RFC6979_SIGNATURE_SIZE / 2) - 1));
rs[1] = fromUnsignedByteArray(Arrays.copyOfRange(sig, RFC6979_SIGNATURE_SIZE / 2, RFC6979_SIGNATURE_SIZE - 1));
return rs;
}
public static void checkResponse(AbstractMessage response) {
if (!verify(response)) {
throw new IllegalArgumentException("Invalid response");
}
var metaHeader = (frostFS.session.Types.ResponseMetaHeader) response
.getField(response.getDescriptorForType().findFieldByName("meta_header"));
var status = StatusMapper.toModel(metaHeader.getStatus());
if (!status.isSuccess()) {
throw new IllegalArgumentException(status.toString());
}
}
public static boolean verify(AbstractMessage message) {
var body = (AbstractMessage) message.getField(message.getDescriptorForType().findFieldByName("body"));
var metaHeader = (frostFS.session.Types.ResponseMetaHeader)
message.getField(message.getDescriptorForType().findFieldByName("meta_header"));
var verifyHeader = (frostFS.session.Types.ResponseVerificationHeader)
message.getField(message.getDescriptorForType().findFieldByName("verify_header"));
return verifyMatryoshkaLevel(body, metaHeader, verifyHeader);
}
public static boolean verifyMatryoshkaLevel(AbstractMessage body,
frostFS.session.Types.ResponseMetaHeader meta,
frostFS.session.Types.ResponseVerificationHeader verification) {
if (!verifyMessagePart(verification.getMetaSignature(), meta)) return false;
var origin = verification.getOrigin();
if (!verifyMessagePart(verification.getOriginSignature(), origin)) return false;
if (origin.getSerializedSize() == 0) {
return verifyMessagePart(verification.getBodySignature(), body);
}
return verification.getBodySignature().getSerializedSize() == 0
&& verifyMatryoshkaLevel(body, meta.getOrigin(), origin);
}
public static boolean verifyMessagePart(Types.Signature sig, AbstractMessage data) {
if (sig.getSerializedSize() == 0 || sig.getKey().isEmpty() || sig.getSign().isEmpty()) return false;
var publicKey = getPublicKeyByPublic(sig.getKey().toByteArray());
var data2Verify = data.getSerializedSize() == 0 ? new byte[]{} : data.toByteArray();
return verifyData(publicKey, data2Verify, sig.getSign().toByteArray());
}
public static boolean verifyData(PublicKey publicKey, byte[] data, byte[] sig) {
try {
Signature signature = Signature.getInstance("NONEwithECDSAinP1363Format");
signature.initVerify(publicKey);
signature.update(DigestUtils.sha512(data));
return signature.verify(Arrays.copyOfRange(sig, 1, sig.length));
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
}

View file

@ -0,0 +1,16 @@
package info.FrostFS.sdk.services;
import info.FrostFS.sdk.jdo.Container;
import info.FrostFS.sdk.jdo.ContainerId;
import java.util.List;
public interface ContainerClient {
Container getContainerAsync(ContainerId cid);
List<ContainerId> listContainersAsync();
ContainerId createContainerAsync(Container container);
void deleteContainerAsync(ContainerId cid);
}

View file

@ -0,0 +1,19 @@
package info.FrostFS.sdk.services;
import info.FrostFS.sdk.jdo.*;
import java.io.FileInputStream;
public interface ObjectClient {
ObjectHeader getObjectHeadAsync(ContainerId containerId, ObjectId objectId);
ObjectFrostFs getObjectAsync(ContainerId containerId, ObjectId objectId);
ObjectId putObjectAsync(ObjectHeader header, FileInputStream payload);
ObjectId putObjectAsync(ObjectHeader header, byte[] payload);
void deleteObjectAsync(ContainerId containerId, ObjectId objectId);
Iterable<ObjectId> searchObjectsAsync(ContainerId cid, ObjectFilter... filters);
}

View file

@ -0,0 +1,107 @@
package info.FrostFS.sdk.services.impl;
import frostFS.container.ContainerServiceGrpc;
import frostFS.container.Service;
import info.FrostFS.sdk.Client;
import info.FrostFS.sdk.Verifier;
import info.FrostFS.sdk.jdo.Container;
import info.FrostFS.sdk.jdo.ContainerId;
import info.FrostFS.sdk.mappers.ContainerIdMapper;
import info.FrostFS.sdk.mappers.ContainerMapper;
import info.FrostFS.sdk.mappers.OwnerIdMapper;
import info.FrostFS.sdk.mappers.VersionMapper;
import info.FrostFS.sdk.services.ContainerClient;
import java.util.List;
import java.util.stream.Collectors;
import static info.FrostFS.sdk.RequestConstructor.addMetaHeader;
import static info.FrostFS.sdk.RequestSigner.sign;
import static info.FrostFS.sdk.RequestSigner.signRFC6979;
public class ContainerService implements ContainerClient {
private final ContainerServiceGrpc.ContainerServiceBlockingStub containerServiceAsyncClient;
private final Client client;
public ContainerService(ContainerServiceGrpc.ContainerServiceBlockingStub containerServiceAsyncClient, Client client) {
this.containerServiceAsyncClient = containerServiceAsyncClient;
this.client = client;
}
public Container getContainerAsync(ContainerId cid) {
var request = Service.GetRequest.newBuilder()
.setBody(
Service.GetRequest.Body.newBuilder().setContainerId(ContainerIdMapper.toGrpcMessage(cid)).build()
);
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = containerServiceAsyncClient.get(request.build());
Verifier.checkResponse(response);
return ContainerMapper.toModel(response.getBody().getContainer());
}
public List<ContainerId> listContainersAsync() {
var request = Service.ListRequest.newBuilder()
.setBody(
Service.ListRequest.Body.newBuilder().setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId())).build()
);
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = containerServiceAsyncClient.list(request.build());
Verifier.checkResponse(response);
return response.getBody().getContainerIdsList().stream()
.map(cid -> ContainerId.fromHash(cid.getValue().toByteArray()))
.collect(Collectors.toList());
}
public ContainerId createContainerAsync(Container container) {
var grpcContainer = ContainerMapper.toGrpcMessage(container);
grpcContainer = grpcContainer.toBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId()))
.setVersion(VersionMapper.toGrpcMessage(client.getVersion()))
.build();
var request = Service.PutRequest.newBuilder()
.setBody(
Service.PutRequest.Body.newBuilder()
.setContainer(grpcContainer)
.setSignature(signRFC6979(client.getPublicKey(), client.getPrivateKey(), grpcContainer))
.build()
);
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = containerServiceAsyncClient.put(request.build());
Verifier.checkResponse(response);
return ContainerId.fromHash(response.getBody().getContainerId().getValue().toByteArray());
}
public void deleteContainerAsync(ContainerId cid) {
var grpcContainerId = ContainerIdMapper.toGrpcMessage(cid);
var request = Service.DeleteRequest.newBuilder()
.setBody(
Service.DeleteRequest.Body.newBuilder()
.setContainerId(grpcContainerId)
.setSignature(signRFC6979(client.getPublicKey(), client.getPrivateKey(), grpcContainerId.getValue()))
.build()
);
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = containerServiceAsyncClient.delete(request.build());
Verifier.checkResponse(response);
}
}

View file

@ -0,0 +1,85 @@
package info.FrostFS.sdk.services.impl;
import info.FrostFS.sdk.Client;
import info.FrostFS.sdk.GrpcClient;
import info.FrostFS.sdk.jdo.*;
import info.FrostFS.sdk.services.ContainerClient;
import info.FrostFS.sdk.services.ObjectClient;
import java.io.FileInputStream;
import java.util.List;
public class FrostFSClient implements ContainerClient, ObjectClient {
private final ContainerService containerService;
private final NetmapService netmapService;
private final ObjectService objectService;
public FrostFSClient(GrpcClient grpcClient, Client client) {
this.containerService = new ContainerService(grpcClient.getContainerServiceBlockingClient(), client);
this.netmapService = new NetmapService(grpcClient.getNetmapServiceBlockingClient(), client);
SessionService sessionService = new SessionService(grpcClient.getSessionServiceBlockingClient(), client);
this.objectService = new ObjectService(
grpcClient.getObjectServiceBlockingClient(), grpcClient.getObjectServiceClient(), sessionService, client
);
checkFrostFsVersionSupport(client);
}
private void checkFrostFsVersionSupport(Client client) {
var localNodeInfo = netmapService.getLocalNodeInfoAsync();
if (!localNodeInfo.getVersion().isSupported(client.getVersion())) {
var msg = String.format("FrostFS %s is not supported.", localNodeInfo.getVersion());
System.out.println(msg);
throw new IllegalArgumentException(msg);
}
}
@Override
public Container getContainerAsync(ContainerId cid) {
return containerService.getContainerAsync(cid);
}
@Override
public List<ContainerId> listContainersAsync() {
return containerService.listContainersAsync();
}
@Override
public ContainerId createContainerAsync(Container container) {
return containerService.createContainerAsync(container);
}
@Override
public void deleteContainerAsync(ContainerId cid) {
containerService.deleteContainerAsync(cid);
}
@Override
public ObjectHeader getObjectHeadAsync(ContainerId containerId, ObjectId objectId) {
return objectService.getObjectHeadAsync(containerId, objectId);
}
@Override
public ObjectFrostFs getObjectAsync(ContainerId containerId, ObjectId objectId) {
return objectService.getObjectAsync(containerId, objectId);
}
@Override
public ObjectId putObjectAsync(ObjectHeader header, FileInputStream payload) {
return objectService.putObjectAsync(header, payload);
}
@Override
public ObjectId putObjectAsync(ObjectHeader header, byte[] payload) {
return objectService.putObjectAsync(header, payload);
}
@Override
public void deleteObjectAsync(ContainerId containerId, ObjectId objectId) {
objectService.deleteObjectAsync(containerId, objectId);
}
@Override
public Iterable<ObjectId> searchObjectsAsync(ContainerId cid, ObjectFilter... filters) {
return objectService.searchObjectsAsync(cid, filters);
}
}

View file

@ -0,0 +1,42 @@
package info.FrostFS.sdk.services.impl;
import frostFS.netmap.NetmapServiceGrpc;
import frostFS.netmap.Service;
import info.FrostFS.sdk.Client;
import info.FrostFS.sdk.jdo.netmap.NodeInfo;
import info.FrostFS.sdk.mappers.netmap.NodeInfoMapper;
import static info.FrostFS.sdk.RequestConstructor.addMetaHeader;
import static info.FrostFS.sdk.RequestSigner.sign;
public class NetmapService {
private final NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceAsyncClient;
private final Client client;
public NetmapService(NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceAsyncClient, Client client) {
this.netmapServiceAsyncClient = netmapServiceAsyncClient;
this.client = client;
}
public NodeInfo getLocalNodeInfoAsync() {
var request = Service.LocalNodeInfoRequest.newBuilder()
.setBody(Service.LocalNodeInfoRequest.Body.newBuilder().build());
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = netmapServiceAsyncClient.localNodeInfo(request.build());
return NodeInfoMapper.toModel(response.getBody());
}
public Service.NetworkInfoResponse getNetworkInfoAsync() {
var request = Service.NetworkInfoRequest.newBuilder()
.setBody(Service.NetworkInfoRequest.Body.newBuilder().build());
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
return netmapServiceAsyncClient.networkInfo(request.build());
}
}

View file

@ -0,0 +1,48 @@
package info.FrostFS.sdk.services.impl;
import frostFS.object.Service;
import frostFS.object.Types;
import info.FrostFS.sdk.Verifier;
import java.util.Iterator;
public class ObjectReader {
public Iterator<Service.GetResponse> call;
public ObjectReader(Iterator<Service.GetResponse> call) {
this.call = call;
}
public Types.Object readHeader() {
if (!call.hasNext()) {
throw new IllegalArgumentException("unexpected end of stream");
}
var response = call.next();
Verifier.checkResponse(response);
if (response.getBody().getObjectPartCase().getNumber() != Service.GetResponse.Body.INIT_FIELD_NUMBER) {
throw new IllegalArgumentException("unexpected message type");
}
return Types.Object.newBuilder()
.setObjectId(response.getBody().getInit().getObjectId())
.setHeader(response.getBody().getInit().getHeader())
.build();
}
public byte[] readChunk() {
if (!call.hasNext()) {
return null;
}
var response = call.next();
Verifier.checkResponse(response);
if (response.getBody().getObjectPartCase().getNumber() != Service.GetResponse.Body.CHUNK_FIELD_NUMBER) {
throw new IllegalArgumentException("unexpected message type");
}
return response.getBody().getChunk().toByteArray();
}
}

View file

@ -0,0 +1,257 @@
package info.FrostFS.sdk.services.impl;
import com.google.common.collect.Iterables;
import com.google.protobuf.ByteString;
import frostFS.object.ObjectServiceGrpc;
import frostFS.object.Service;
import frostFS.refs.Types;
import info.FrostFS.sdk.Client;
import info.FrostFS.sdk.Verifier;
import info.FrostFS.sdk.jdo.*;
import info.FrostFS.sdk.mappers.*;
import info.FrostFS.sdk.services.ObjectClient;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static info.FrostFS.sdk.Helper.getSha256;
import static info.FrostFS.sdk.RequestConstructor.addMetaHeader;
import static info.FrostFS.sdk.RequestConstructor.addObjectSessionToken;
import static info.FrostFS.sdk.RequestSigner.sign;
import static java.util.Objects.nonNull;
public class ObjectService implements ObjectClient {
private final ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceBlockingClient;
private final ObjectServiceGrpc.ObjectServiceStub objectServiceClient;
private final SessionService sessionService;
private final Client client;
public ObjectService(ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceAsyncClient,
ObjectServiceGrpc.ObjectServiceStub objectServiceClient,
SessionService sessionService,
Client client) {
this.objectServiceBlockingClient = objectServiceAsyncClient;
this.objectServiceClient = objectServiceClient;
this.client = client;
this.sessionService = sessionService;
}
@Override
public ObjectHeader getObjectHeadAsync(ContainerId cid, ObjectId oid) {
var request = Service.HeadRequest.newBuilder()
.setBody(
Service.HeadRequest.Body.newBuilder()
.setAddress(
Types.Address.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setObjectId(ObjectIdMapper.toGrpcMessage(oid))
.build()
).build()
);
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = objectServiceBlockingClient.head(request.build());
Verifier.checkResponse(response);
return ObjectHeaderMapper.toModel(response.getBody().getHeader().getHeader());
}
@Override
public ObjectFrostFs getObjectAsync(ContainerId cid, ObjectId oid) {
var sessionToken = sessionService.createSessionAsync(-1);
var request = Service.GetRequest.newBuilder()
.setBody(
Service.GetRequest.Body.newBuilder()
.setAddress(
Types.Address.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setObjectId(ObjectIdMapper.toGrpcMessage(oid))
.build()
)
.setRaw(false)
.build()
);
addMetaHeader(request, null);
addObjectSessionToken(
request, sessionToken, ContainerIdMapper.toGrpcMessage(cid), ObjectIdMapper.toGrpcMessage(oid),
frostFS.session.Types.ObjectSessionContext.Verb.GET, client.getPublicKey(), client.getPrivateKey()
);
sign(request, client.getPublicKey(), client.getPrivateKey());
var obj = getObject(request.build());
return ObjectMapper.toModel(obj);
}
@Override
public void deleteObjectAsync(ContainerId cid, ObjectId oid) {
var request = Service.DeleteRequest.newBuilder()
.setBody(
Service.DeleteRequest.Body.newBuilder()
.setAddress(
Types.Address.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setObjectId(ObjectIdMapper.toGrpcMessage(oid))
.build()
)
.build());
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var response = objectServiceBlockingClient.delete(request.build());
Verifier.checkResponse(response);
}
@Override
public ObjectId putObjectAsync(ObjectHeader header, FileInputStream payload) {
return putObject(header, payload);
}
@Override
public ObjectId putObjectAsync(ObjectHeader header, byte[] payload) {
return putObject(header, new ByteArrayInputStream(payload));
}
@Override
public Iterable<ObjectId> searchObjectsAsync(ContainerId cid, ObjectFilter... filters) {
var body = Service.SearchRequest.Body.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setVersion(1);
for (ObjectFilter filter : filters) {
body.addFilters(ObjectFilterMapper.toGrpcMessage(filter));
}
var request = Service.SearchRequest.newBuilder()
.setBody(body.build());
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
var objectsIds = searchObjects(request.build());
return Iterables.transform(objectsIds, input -> ObjectId.fromHash(input.getValue().toByteArray()));
}
private frostFS.object.Types.Object getObject(Service.GetRequest request) {
var iterator = getObjectInit(request);
var obj = iterator.readHeader();
var payload = new byte[Math.toIntExact(obj.getHeader().getPayloadLength())];
var offset = 0;
var chunk = iterator.readChunk();
while (nonNull(chunk)) {
System.arraycopy(chunk, 0, payload, offset, chunk.length);
offset += chunk.length;
chunk = iterator.readChunk();
}
return obj.toBuilder().setPayload(ByteString.copyFrom(payload)).build();
}
private ObjectReader getObjectInit(Service.GetRequest initRequest) {
if (initRequest.getSerializedSize() == 0) {
throw new IllegalArgumentException(initRequest.getClass().getName());
}
return new ObjectReader(objectServiceBlockingClient.get(initRequest));
}
private ObjectId putObject(ObjectHeader header, InputStream payload) {
var sessionToken = sessionService.createSessionAsync(-1);
var hdr = ObjectHeaderMapper.toGrpcMessage(header);
hdr = hdr.toBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId()))
.setVersion(VersionMapper.toGrpcMessage(client.getVersion()))
.build();
var oid = Types.ObjectID.newBuilder().setValue(getSha256(hdr)).build();
var request = Service.PutRequest.newBuilder()
.setBody(
Service.PutRequest.Body.newBuilder()
.setInit(
Service.PutRequest.Body.Init.newBuilder().setHeader(hdr).build()
).build()
);
addMetaHeader(request, null);
addObjectSessionToken(
request, sessionToken, hdr.getContainerId(), oid, frostFS.session.Types.ObjectSessionContext.Verb.PUT,
client.getPublicKey(), client.getPrivateKey()
);
sign(request, client.getPublicKey(), client.getPrivateKey());
var writer = putObjectInit(request.build());
var buffer = new byte[Constants.OBJECT_CHUNK_SIZE];
int bufferLength = 0;
try {
bufferLength = payload.readNBytes(buffer, 0, Constants.OBJECT_CHUNK_SIZE);
while (bufferLength > 0) {
request.setBody(
Service.PutRequest.Body.newBuilder()
.setChunk(ByteString.copyFrom(Arrays.copyOfRange(buffer, 0, bufferLength)))
.build()
)
.clearVerifyHeader();
sign(request, client.getPublicKey(), client.getPrivateKey());
writer.write(request.build());
bufferLength = payload.readNBytes(buffer, 0, Constants.OBJECT_CHUNK_SIZE);
}
} catch (
IOException e) {
throw new RuntimeException(e);
}
var response = writer.complete();
Verifier.checkResponse(response);
return ObjectId.fromHash(response.getBody().getObjectId().getValue().toByteArray());
}
private ObjectWriter putObjectInit(Service.PutRequest initRequest) {
if (initRequest.getSerializedSize() == 0) {
throw new IllegalArgumentException(initRequest.getClass().getName());
}
ObjectWriter writer = new ObjectWriter(objectServiceClient);
writer.write(initRequest);
return writer;
}
private Iterable<Types.ObjectID> searchObjects(Service.SearchRequest request) {
var reader = getSearchReader(request);
var ids = reader.read();
List<Types.ObjectID> result = new ArrayList<>();
while (nonNull(ids) && !ids.isEmpty()) {
result.addAll(ids);
ids = reader.read();
}
return result;
}
private SearchReader getSearchReader(Service.SearchRequest initRequest) {
if (initRequest.getSerializedSize() == 0) {
throw new IllegalArgumentException(initRequest.getClass().getName());
}
return new SearchReader(objectServiceBlockingClient.search(initRequest));
}
}

View file

@ -0,0 +1,59 @@
package info.FrostFS.sdk.services.impl;
import frostFS.object.ObjectServiceGrpc;
import frostFS.object.Service;
import io.grpc.stub.StreamObserver;
import static java.util.Objects.isNull;
public class ObjectWriter {
private final StreamObserver<Service.PutRequest> requestObserver;
private final PutResponseCallback responseObserver;
public ObjectWriter(ObjectServiceGrpc.ObjectServiceStub objectServiceStub) {
PutResponseCallback responseObserver = new PutResponseCallback();
this.responseObserver = responseObserver;
this.requestObserver = objectServiceStub.put(responseObserver);
}
public void write(Service.PutRequest request) {
if (isNull(request)) {
throw new IllegalArgumentException();
}
requestObserver.onNext(request);
}
public Service.PutResponse complete() {
requestObserver.onCompleted();
while (isNull(responseObserver.getResponse())) {
System.out.println("Waiting response");
}
return responseObserver.getResponse();
}
private static class PutResponseCallback implements StreamObserver<Service.PutResponse> {
private Service.PutResponse response;
@Override
public void onNext(Service.PutResponse putResponse) {
this.response = putResponse;
}
@Override
public void onError(Throwable throwable) {
throw new RuntimeException(throwable);
}
@Override
public void onCompleted() {
}
public Service.PutResponse getResponse() {
return response;
}
}
}

View file

@ -0,0 +1,26 @@
package info.FrostFS.sdk.services.impl;
import frostFS.object.Service;
import info.FrostFS.sdk.Verifier;
import java.util.Iterator;
import java.util.List;
public class SearchReader {
public Iterator<Service.SearchResponse> call;
public SearchReader(Iterator<Service.SearchResponse> call) {
this.call = call;
}
public List<frostFS.refs.Types.ObjectID> read() {
if (!call.hasNext()) {
return null;
}
var response = call.next();
Verifier.checkResponse(response);
return response.getBody().getIdListList();
}
}

View file

@ -0,0 +1,55 @@
package info.FrostFS.sdk.services.impl;
import frostFS.session.Service;
import frostFS.session.SessionServiceGrpc;
import frostFS.session.Types;
import info.FrostFS.sdk.Client;
import info.FrostFS.sdk.mappers.OwnerIdMapper;
import static info.FrostFS.sdk.RequestConstructor.addMetaHeader;
import static info.FrostFS.sdk.RequestSigner.sign;
public class SessionService {
private final SessionServiceGrpc.SessionServiceBlockingStub sessionServiceAsyncClient;
private final Client client;
public SessionService(SessionServiceGrpc.SessionServiceBlockingStub sessionServiceAsyncClient, Client client) {
this.sessionServiceAsyncClient = sessionServiceAsyncClient;
this.client = client;
}
protected Types.SessionToken createSessionAsync(long expiration) {
var request = Service.CreateRequest.newBuilder()
.setBody(
Service.CreateRequest.Body.newBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId()))
.setExpiration(expiration).build()
);
addMetaHeader(request, null);
sign(request, client.getPublicKey(), client.getPrivateKey());
return createSession(request.build());
}
private Types.SessionToken createSession(Service.CreateRequest request) {
var resp = sessionServiceAsyncClient.create(request);
var lifetime = Types.SessionToken.Body.TokenLifetime.newBuilder()
.setExp(request.getBody().getExpiration())
.setIat(resp.getMetaHeader().getEpoch())
.setNbf(resp.getMetaHeader().getEpoch())
.build();
var body = Types.SessionToken.Body.newBuilder()
.setId(resp.getBody().getId())
.setSessionKey(resp.getBody().getSessionKey())
.setOwnerId(request.getBody().getOwnerId())
.setLifetime(lifetime)
.build();
return Types.SessionToken.newBuilder()
.setBody(body)
.build();
}
}

44
cryptography/pom.xml Normal file
View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>FrostFS-sdk-java</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>cryptography</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<protobuf.version>3.23.0</protobuf.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.17.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,12 @@
package info.FrostFS.sdk;
public class ArrayHelper {
public static byte[] concat(byte[] startArray, byte[] endArray) {
byte[] result = new byte[startArray.length + endArray.length];
System.arraycopy(startArray, 0, result, 0, startArray.length);
System.arraycopy(endArray, 0, result, startArray.length, endArray.length);
return result;
}
}

View file

@ -0,0 +1,127 @@
package info.FrostFS.sdk;
import java.util.Arrays;
import static info.FrostFS.sdk.ArrayHelper.concat;
import static info.FrostFS.sdk.Helper.getSha256;
import static java.util.Objects.isNull;
public class Base58 {
public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final char ENCODED_ZERO = ALPHABET[0];
private static final int[] INDEXES = new int[128];
static {
Arrays.fill(INDEXES, -1);
for (int i = 0; i < ALPHABET.length; i++) {
INDEXES[ALPHABET[i]] = i;
}
}
public static byte[] base58CheckDecode(String input) {
if (isNull(input) || input.isEmpty()) {
throw new IllegalArgumentException("Input value is missing");
}
byte[] buffer = decode(input);
if (buffer.length < 4) {
throw new IllegalArgumentException();
}
byte[] decode = Arrays.copyOfRange(buffer, 0, buffer.length - 4);
byte[] checksum = getSha256(getSha256(decode));
if (!Arrays.equals(
Arrays.copyOfRange(buffer, buffer.length - 4, buffer.length),
Arrays.copyOfRange(checksum, 0, 4)
)) {
throw new IllegalArgumentException();
}
return decode;
}
public static String base58CheckEncode(byte[] data) {
byte[] checksum = getSha256(getSha256(data));
var buffer = concat(data, Arrays.copyOfRange(checksum, 0, 4));
var ret = encode(buffer);
return ret;
}
public static String encode(byte[] input) {
if (input.length == 0) {
return "";
}
// Count leading zeros.
int zeros = 0;
while (zeros < input.length && input[zeros] == 0) {
++zeros;
}
// Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
input = Arrays.copyOf(input, input.length); // since we modify it in-place
char[] encoded = new char[input.length * 2]; // upper bound
int outputStart = encoded.length;
for (int inputStart = zeros; inputStart < input.length; ) {
encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
if (input[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
++outputStart;
}
while (--zeros >= 0) {
encoded[--outputStart] = ENCODED_ZERO;
}
// Return encoded string (including encoded leading zeros).
return new String(encoded, outputStart, encoded.length - outputStart);
}
public static byte[] decode(String input) {
if (input.isEmpty()) {
return new byte[0];
}
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
byte[] input58 = new byte[input.length()];
for (int i = 0; i < input.length(); ++i) {
char c = input.charAt(i);
int digit = c < 128 ? INDEXES[c] : -1;
if (digit < 0) {
throw new IllegalArgumentException(String.format("Invalid character in Base58: 0x%04x", (int) c));
}
input58[i] = (byte) digit;
}
// Count leading zeros.
int zeros = 0;
while (zeros < input58.length && input58[zeros] == 0) {
++zeros;
}
// Convert base-58 digits to base-256 digits.
byte[] decoded = new byte[input.length()];
int outputStart = decoded.length;
for (int inputStart = zeros; inputStart < input58.length; ) {
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
if (input58[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Ignore extra leading zeroes that were added during the calculation.
while (outputStart < decoded.length && decoded[outputStart] == 0) {
++outputStart;
}
// Return decoded data (including original number of leading zeros).
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
}
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
// this is just long division which accounts for the base of the input digits
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = (int) number[i] & 0xFF;
int temp = remainder * base + digit;
number[i] = (byte) (temp / divisor);
remainder = temp % divisor;
}
return (byte) remainder;
}
}

View file

@ -0,0 +1,36 @@
package info.FrostFS.sdk;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ByteString;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Helper {
public static byte[] getRIPEMD160(byte[] value) {
var hash = new byte[20];
var digest = new RIPEMD160Digest();
digest.update(value, 0, value.length);
digest.doFinal(hash, 0);
return hash;
}
public static byte[] getSha256(byte[] value) {
try {
return MessageDigest.getInstance("SHA-256").digest(value);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static ByteString getSha256(AbstractMessage value) {
return ByteString.copyFrom(getSha256(value.toByteArray()));
}
public static String пуеHexString(byte[] array) {
return String.format("%0" + (array.length << 1) + "x", new BigInteger(1, array));
}
}

View file

@ -0,0 +1,161 @@
package info.FrostFS.sdk;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import static info.FrostFS.sdk.Helper.getRIPEMD160;
import static info.FrostFS.sdk.Helper.getSha256;
import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray;
public class KeyExtension {
public static final byte NEO_ADDRESS_VERSION = 0x35;
private static final int COMPRESSED_PUBLIC_KEY_LENGTH = 33;
private static final int UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65;
private static final int CHECK_SIG_DESCRIPTOR = ByteBuffer
.wrap(getSha256("System.Crypto.CheckSig".getBytes(StandardCharsets.US_ASCII)))
.order(ByteOrder.LITTLE_ENDIAN).getInt();
public static byte[] compress(byte[] publicKey) {
if (publicKey.length != UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
String.format("Compress argument isn't uncompressed public key. Expected length=%s, actual=%s",
UNCOMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length)
);
}
var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1);
var point = secp256R1.getCurve().decodePoint(publicKey);
return point.getEncoded(true);
}
public static byte[] getPrivateKeyFromWIF(String wif) {
var data = Base58.base58CheckDecode(wif);
return Arrays.copyOfRange(data, 1, data.length - 1);
}
public static byte[] loadPublicKey(byte[] privateKey) {
X9ECParameters params = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1);
ECDomainParameters domain = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
ECPoint q = domain.getG().multiply(new BigInteger(1, privateKey));
ECPublicKeyParameters publicParams = new ECPublicKeyParameters(q, domain);
return publicParams.getQ().getEncoded(true);
}
public static PrivateKey loadPrivateKey(byte[] privateKey) {
X9ECParameters params = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1);
ECDomainParameters domain = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
ECPrivateKeyParameters ecParams = new ECPrivateKeyParameters(fromUnsignedByteArray(privateKey), domain);
ECParameterSpec ecParameterSpec = new ECNamedCurveSpec(
"secp256r1", params.getCurve(), params.getG(), params.getN(), params.getH()
);
ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(ecParams.getD(), ecParameterSpec);
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
public static PublicKey getPublicKeyByPublic(byte[] publicKey) {
if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
String.format("Decompress argument isn't compressed public key. Expected length=%s, actual=%s",
COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length)
);
}
X9ECParameters secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1);
ECDomainParameters domain = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH());
var ecPoint = secp256R1.getCurve().decodePoint(publicKey);
var publicParams = new ECPublicKeyParameters(ecPoint, domain);
java.security.spec.ECPoint point = new java.security.spec.ECPoint(
publicParams.getQ().getRawXCoord().toBigInteger(), publicParams.getQ().getRawYCoord().toBigInteger()
);
ECParameterSpec ecParameterSpec = new ECNamedCurveSpec(
"secp256r1", secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH(), secp256R1.getSeed()
);
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecParameterSpec);
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(publicKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static byte[] getScriptHash(byte[] publicKey) {
var script = createSignatureRedeemScript(publicKey);
return getRIPEMD160(getSha256(script));
}
public static String publicKeyToAddress(byte[] publicKey) {
if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
String.format("PublicKey isn't encoded compressed public key. Expected length=%s, actual=%s",
COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length)
);
}
return toAddress(getScriptHash(publicKey), NEO_ADDRESS_VERSION);
}
private static String toAddress(byte[] scriptHash, byte version) {
byte[] data = new byte[21];
data[0] = version;
System.arraycopy(scriptHash, 0, data, 1, scriptHash.length);
return Base58.base58CheckEncode(data);
}
private static byte[] getBytes(int value) {
byte[] buffer = new byte[4];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = (byte) (value >> i * 8);
}
return buffer;
}
private static byte[] createSignatureRedeemScript(byte[] publicKey) {
if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(
String.format("PublicKey isn't encoded compressed public key. Expected length=%s, actual=%s",
COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length)
);
}
var script = new byte[]{0x0c, COMPRESSED_PUBLIC_KEY_LENGTH}; //PUSHDATA1 33
script = ArrayHelper.concat(script, publicKey);
script = ArrayHelper.concat(script, new byte[]{0x41}); //SYSCALL
script = ArrayHelper.concat(script, getBytes(CHECK_SIG_DESCRIPTOR)); //Neo_Crypto_CheckSig
return script;
}
}

38
modelsV2/pom.xml Normal file
View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>FrostFS-sdk-java</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>modelsV2</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>cryptography</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>protosV2</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,21 @@
package info.FrostFS.sdk;
import java.nio.ByteBuffer;
import java.util.UUID;
public class UUIDExtension {
public static UUID asUuid(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
long firstLong = bb.getLong();
long secondLong = bb.getLong();
return new UUID(firstLong, secondLong);
}
public static byte[] asBytes(UUID id) {
ByteBuffer bb = ByteBuffer.allocate(16);
bb.putLong(id.getMostSignificantBits());
bb.putLong(id.getLeastSignificantBits());
return bb.array();
}
}

View file

@ -0,0 +1,7 @@
package info.FrostFS.sdk.constants;
public class XHeaderConst {
public static final String RESERVED_XHEADER_PREFIX = "__SYSTEM__";
public static final String XHEADER_NETMAP_EPOCH = RESERVED_XHEADER_PREFIX + "NETMAP_EPOCH";
public static final String XHEADER_NETMAP_LOOKUP_DEPTH = RESERVED_XHEADER_PREFIX + "NETMAP_LOOKUP_DEPTH";
}

View file

@ -0,0 +1,33 @@
package info.FrostFS.sdk.enums;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public enum BasicAcl {
PRIVATE(0x1C8C8CCC),
PUBLIC_RO(0x1FBF8CFF),
PUBLIC_RW(0x1FBFBFFF),
PUBLIC_APPEND(0x1FBF9FFF),
;
private static final Map<Integer, BasicAcl> ENUM_MAP_BY_VALUE;
static {
Map<Integer, BasicAcl> map = new HashMap<>();
for (BasicAcl basicAcl : BasicAcl.values()) {
map.put(basicAcl.value, basicAcl);
}
ENUM_MAP_BY_VALUE = Collections.unmodifiableMap(map);
}
public final int value;
BasicAcl(int value) {
this.value = value;
}
public static BasicAcl get(int value) {
return ENUM_MAP_BY_VALUE.get(value);
}
}

View file

@ -0,0 +1,33 @@
package info.FrostFS.sdk.enums;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public enum NodeState {
UNSPECIFIED(0),
ONLINE(1),
OFFLINE(2),
MAINTENANCE(3),
;
private static final Map<Integer, NodeState> ENUM_MAP_BY_VALUE;
static {
Map<Integer, NodeState> map = new HashMap<>();
for (NodeState nodeState : NodeState.values()) {
map.put(nodeState.value, nodeState);
}
ENUM_MAP_BY_VALUE = Collections.unmodifiableMap(map);
}
public final int value;
NodeState(int value) {
this.value = value;
}
public static NodeState get(int value) {
return ENUM_MAP_BY_VALUE.get(value);
}
}

View file

@ -0,0 +1,16 @@
package info.FrostFS.sdk.enums;
public enum ObjectMatchType {
UNSPECIFIED(0),
EQUALS(1),
NOT_EQUALS(2),
KEY_ABSENT(3),
STARTS_WITH(4),
;
public final int value;
ObjectMatchType(int value) {
this.value = value;
}
}

View file

@ -0,0 +1,32 @@
package info.FrostFS.sdk.enums;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public enum ObjectType {
REGULAR(0),
TOMBSTONE(1),
LOCK(3),
;
private static final Map<Integer, ObjectType> ENUM_MAP_BY_VALUE;
static {
Map<Integer, ObjectType> map = new HashMap<>();
for (ObjectType objectType : ObjectType.values()) {
map.put(objectType.value, objectType);
}
ENUM_MAP_BY_VALUE = Collections.unmodifiableMap(map);
}
public final int value;
ObjectType(int value) {
this.value = value;
}
public static ObjectType get(int value) {
return ENUM_MAP_BY_VALUE.get(value);
}
}

View file

@ -0,0 +1,46 @@
package info.FrostFS.sdk.enums;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public enum StatusCode {
SUCCESS(0),
INTERNAL(1024),
WRONG_MAGIC_NUMBER(1025),
SIGNATURE_VERIFICATION_FAILURE(1026),
NODE_UNDER_MAINTENANCE(1027),
OBJECT_ACCESS_DENIED(2048),
OBJECT_NOT_FOUND(2049),
OBJECT_LOCKED(2050),
LOCK_NOT_REGULAR_OBJECT(2051),
OBJECT_ALREADY_REMOVED(2052),
OUT_OF_RANGE(2053),
CONTAINER_NOT_FOUND(3072),
E_ACL_NOT_FOUND(3073),
CONTAINER_ACCESS_DENIED(3074),
TOKEN_NOT_FOUND(4096),
TOKEN_EXPIRED(4097),
APE_MANAGER_ACCESS_DENIED(5120),
;
private static final Map<Integer, StatusCode> ENUM_MAP_BY_VALUE;
static {
Map<Integer, StatusCode> map = new HashMap<>();
for (StatusCode statusCode : StatusCode.values()) {
map.put(statusCode.value, statusCode);
}
ENUM_MAP_BY_VALUE = Collections.unmodifiableMap(map);
}
public final int value;
StatusCode(int value) {
this.value = value;
}
public static StatusCode get(int value) {
return ENUM_MAP_BY_VALUE.get(value);
}
}

View file

@ -0,0 +1,6 @@
package info.FrostFS.sdk.jdo;
public class Constants {
public static final int OBJECT_CHUNK_SIZE = 3 * (1 << 20);
public static final int SHA256_HASH_LENGTH = 32;
}

View file

@ -0,0 +1,58 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.enums.BasicAcl;
import info.FrostFS.sdk.jdo.netmap.PlacementPolicy;
import java.util.UUID;
public class Container {
public UUID nonce;
public BasicAcl basicAcl;
public PlacementPolicy placementPolicy;
public Version version;
public UUID getNonce() {
return nonce;
}
public void setNonce(UUID nonce) {
this.nonce = nonce;
}
public BasicAcl getBasicAcl() {
return basicAcl;
}
public void setBasicAcl(BasicAcl basicAcl) {
this.basicAcl = basicAcl;
}
public PlacementPolicy getPlacementPolicy() {
return placementPolicy;
}
public void setPlacementPolicy(PlacementPolicy placementPolicy) {
this.placementPolicy = placementPolicy;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) {
nonce = UUID.randomUUID();
this.basicAcl = basicAcl;
this.placementPolicy = placementPolicy;
}
public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy, UUID nonce, Version version) {
this.nonce = nonce;
this.basicAcl = basicAcl;
this.placementPolicy = placementPolicy;
this.version = version;
}
}

View file

@ -0,0 +1,35 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.Base58;
public class ContainerId {
public String value;
public ContainerId(String value) {
this.value = value;
}
public static ContainerId fromHash(byte[] hash) {
if (hash.length != Constants.SHA256_HASH_LENGTH) {
throw new IllegalArgumentException("ContainerID must be a sha256 hash.");
}
return new ContainerId(Base58.encode(hash));
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
public byte[] toHash() {
return Base58.decode(value);
}
}

View file

@ -0,0 +1,41 @@
package info.FrostFS.sdk.jdo;
public class MetaHeader {
public Version version;
public int epoch;
public int ttl;
public MetaHeader(Version version, int epoch, int ttl) {
this.version = version;
this.epoch = epoch;
this.ttl = ttl;
}
public static MetaHeader getDefault() {
return new MetaHeader(new Version(2, 13), 0, 2);
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public int getEpoch() {
return epoch;
}
public void setEpoch(int epoch) {
this.epoch = epoch;
}
public int getTtl() {
return ttl;
}
public void setTtl(int ttl) {
this.ttl = ttl;
}
}

View file

@ -0,0 +1,27 @@
package info.FrostFS.sdk.jdo;
public class ObjectAttribute {
public String key;
public String value;
public ObjectAttribute(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View file

@ -0,0 +1,58 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.enums.ObjectMatchType;
public class ObjectFilter {
private static final String HEADER_PREFIX = "Object:";
public ObjectMatchType matchType;
public String key;
public String value;
public ObjectFilter(ObjectMatchType matchType, String key, String value) {
this.matchType = matchType;
this.key = key;
this.value = value;
}
public static ObjectFilter ObjectIdFilter(ObjectMatchType matchType, ObjectId objectId) {
return new ObjectFilter(matchType, HEADER_PREFIX + "objectID", objectId.value);
}
public static ObjectFilter OwnerFilter(ObjectMatchType matchType, OwnerId ownerId) {
return new ObjectFilter(matchType, HEADER_PREFIX + "ownerID", ownerId.getValue());
}
public static ObjectFilter RootFilter() {
return new ObjectFilter(ObjectMatchType.UNSPECIFIED, HEADER_PREFIX + "ROOT", "");
}
public static ObjectFilter VersionFilter(ObjectMatchType matchType, Version version) {
return new ObjectFilter(matchType, HEADER_PREFIX + "version", version.toString());
}
public ObjectMatchType getMatchType() {
return matchType;
}
public void setMatchType(ObjectMatchType matchType) {
this.matchType = matchType;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View file

@ -0,0 +1,37 @@
package info.FrostFS.sdk.jdo;
public class ObjectFrostFs {
public ObjectHeader header;
public ObjectId objectId;
public byte[] payload;
public ObjectFrostFs(ObjectHeader header, ObjectId objectId, byte[] payload) {
this.header = header;
this.objectId = objectId;
this.payload = payload;
}
public ObjectHeader getHeader() {
return header;
}
public void setHeader(ObjectHeader header) {
this.header = header;
}
public ObjectId getObjectId() {
return objectId;
}
public void setObjectId(ObjectId objectId) {
this.objectId = objectId;
}
public byte[] getPayload() {
return payload;
}
public void setPayload(byte[] payload) {
this.payload = payload;
}
}

View file

@ -0,0 +1,71 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.enums.ObjectType;
public class ObjectHeader {
public ObjectAttribute[] attributes;
public ContainerId containerId;
public long size;
public ObjectType objectType;
public Version version;
public ObjectHeader(ContainerId containerId, ObjectType objectType, ObjectAttribute[] attributes, long size, Version version) {
this.attributes = attributes;
this.containerId = containerId;
this.size = size;
this.objectType = objectType;
this.version = version;
}
public ObjectHeader(ContainerId containerId, ObjectType type, ObjectAttribute[] attributes) {
this.attributes = attributes;
this.containerId = containerId;
this.objectType = type;
}
public ObjectHeader(ContainerId containerId, ObjectAttribute[] attributes) {
this.attributes = attributes;
this.containerId = containerId;
this.objectType = ObjectType.REGULAR;
}
public ObjectAttribute[] getAttributes() {
return attributes;
}
public void setAttributes(ObjectAttribute[] attributes) {
this.attributes = attributes;
}
public ContainerId getContainerId() {
return containerId;
}
public void setContainerId(ContainerId containerId) {
this.containerId = containerId;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public ObjectType getObjectType() {
return objectType;
}
public void setObjectType(ObjectType objectType) {
this.objectType = objectType;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
}

View file

@ -0,0 +1,31 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.Base58;
public class ObjectId {
public String value;
public ObjectId(String id) {
this.value = id;
}
public static ObjectId fromHash(byte[] hash) {
if (hash.length != Constants.SHA256_HASH_LENGTH) {
throw new IllegalArgumentException("ObjectId must be a sha256 hash.");
}
return new ObjectId(Base58.encode(hash));
}
public String getValue() {
return value;
}
@Override
public String toString() {
return value;
}
public byte[] toHash() {
return Base58.decode(value);
}
}

View file

@ -0,0 +1,25 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.Base58;
import static info.FrostFS.sdk.KeyExtension.publicKeyToAddress;
public class OwnerId {
private final String value;
public OwnerId(String value) {
this.value = value;
}
public static OwnerId fromKey(byte[] publicKey) {
return new OwnerId(publicKeyToAddress(publicKey));
}
public String getValue() {
return value;
}
public byte[] toHash() {
return Base58.decode(value);
}
}

View file

@ -0,0 +1,45 @@
package info.FrostFS.sdk.jdo;
import info.FrostFS.sdk.enums.StatusCode;
import java.util.Optional;
public class Status {
public StatusCode code;
public String message;
public Status(StatusCode code, String message) {
this.code = code;
this.message = Optional.ofNullable(message).orElse("");
}
public Status(StatusCode code) {
this.code = code;
this.message = "";
}
public StatusCode getCode() {
return code;
}
public void setCode(StatusCode code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "Response status: " + code + ". Message: " + message + ".";
}
public boolean isSuccess() {
return StatusCode.SUCCESS.equals(code);
}
}

View file

@ -0,0 +1,36 @@
package info.FrostFS.sdk.jdo;
public class Version {
public int major;
public int minor;
public int getMajor() {
return major;
}
public void setMajor(int major) {
this.major = major;
}
public int getMinor() {
return minor;
}
public void setMinor(int minor) {
this.minor = minor;
}
@Override
public String toString() {
return "v" + major + "." + minor;
}
public Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
public boolean isSupported(Version version) {
return major == version.getMajor();
}
}

View file

@ -0,0 +1,30 @@
package info.FrostFS.sdk.jdo.netmap;
import info.FrostFS.sdk.jdo.Version;
import info.FrostFS.sdk.enums.NodeState;
public class NodeInfo {
public NodeState state;
public Version version;
public NodeState getState() {
return state;
}
public void setState(NodeState state) {
this.state = state;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public NodeInfo(NodeState state, Version version) {
this.state = state;
this.version = version;
}
}

View file

@ -0,0 +1,27 @@
package info.FrostFS.sdk.jdo.netmap;
public class PlacementPolicy {
public Replica[] replicas;
public boolean unique;
public PlacementPolicy(boolean unique, Replica[] replicas) {
this.replicas = replicas;
this.unique = unique;
}
public Replica[] getReplicas() {
return replicas;
}
public void setReplicas(Replica[] replicas) {
this.replicas = replicas;
}
public boolean isUnique() {
return unique;
}
public void setUnique(boolean unique) {
this.unique = unique;
}
}

View file

@ -0,0 +1,34 @@
package info.FrostFS.sdk.jdo.netmap;
import java.util.Optional;
public class Replica {
public int count;
public String selector;
public Replica(int count, String selector) {
this.count = count;
this.selector = Optional.ofNullable(selector).orElse("");
}
public Replica(int count) {
this.count = count;
this.selector = "";
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getSelector() {
return selector;
}
public void setSelector(String selector) {
this.selector = selector;
}
}

View file

@ -0,0 +1,16 @@
package info.FrostFS.sdk.mappers;
import com.google.protobuf.ByteString;
import frostFS.refs.Types;
import info.FrostFS.sdk.jdo.ContainerId;
public class ContainerIdMapper {
public static Types.ContainerID toGrpcMessage(ContainerId containerId) {
var test = containerId.toHash();
return Types.ContainerID.newBuilder()
.setValue(ByteString.copyFrom(containerId.toHash()))
.build();
}
}

View file

@ -0,0 +1,39 @@
package info.FrostFS.sdk.mappers;
import com.google.protobuf.ByteString;
import frostFS.container.Types;
import info.FrostFS.sdk.enums.BasicAcl;
import info.FrostFS.sdk.jdo.Container;
import info.FrostFS.sdk.mappers.netmap.PlacementPolicyMapper;
import static info.FrostFS.sdk.UUIDExtension.asBytes;
import static info.FrostFS.sdk.UUIDExtension.asUuid;
import static java.util.Objects.isNull;
public class ContainerMapper {
public static Types.Container toGrpcMessage(Container container) {
return Types.Container.newBuilder()
.setBasicAcl(container.getBasicAcl().value)
.setPlacementPolicy(PlacementPolicyMapper.toGrpcMessage(container.getPlacementPolicy()))
.setNonce(ByteString.copyFrom(asBytes(container.getNonce())))
.build();
}
public static Container toModel(Types.Container container) {
var basicAcl = BasicAcl.get(container.getBasicAcl());
if (isNull(basicAcl)) {
throw new IllegalArgumentException(
String.format("Unknown BasicACL rule. Value: %s.", container.getBasicAcl())
);
}
return new Container(
basicAcl,
PlacementPolicyMapper.toModel(container.getPlacementPolicy()),
asUuid(container.getNonce().toByteArray()),
VersionMapper.toModel(container.getVersion())
);
}
}

View file

@ -0,0 +1,15 @@
package info.FrostFS.sdk.mappers;
import frostFS.session.Types;
import info.FrostFS.sdk.jdo.MetaHeader;
public class MetaHeaderMapper {
public static Types.RequestMetaHeader toGrpcMessage(MetaHeader metaHeader) {
return Types.RequestMetaHeader.newBuilder()
.setVersion(VersionMapper.toGrpcMessage(metaHeader.getVersion()))
.setEpoch(metaHeader.getEpoch())
.setTtl(metaHeader.getTtl())
.build();
}
}

View file

@ -0,0 +1,17 @@
package info.FrostFS.sdk.mappers;
import frostFS.object.Types;
import info.FrostFS.sdk.jdo.ObjectAttribute;
public class ObjectAttributeMapper {
public static Types.Header.Attribute toGrpcMessage(ObjectAttribute attribute) {
return Types.Header.Attribute.newBuilder()
.setKey(attribute.getKey())
.setValue(attribute.getValue())
.build();
}
public static ObjectAttribute toModel(Types.Header.Attribute attribute) {
return new ObjectAttribute(attribute.getKey(), attribute.getValue());
}
}

View file

@ -0,0 +1,25 @@
package info.FrostFS.sdk.mappers;
import frostFS.object.Service;
import frostFS.object.Types;
import info.FrostFS.sdk.jdo.ObjectFilter;
import org.apache.commons.lang3.EnumUtils;
import static java.util.Objects.isNull;
public class ObjectFilterMapper {
public static Service.SearchRequest.Body.Filter toGrpcMessage(ObjectFilter filter) {
var objectMatchType = Types.MatchType.forNumber(filter.getMatchType().value);
if (isNull(objectMatchType)) {
throw new IllegalArgumentException(
String.format("Unknown MatchType. Value: %s.", filter.getMatchType().name())
);
}
return Service.SearchRequest.Body.Filter.newBuilder()
.setMatchType(objectMatchType)
.setKey(filter.getKey())
.setValue(filter.getValue())
.build();
}
}

View file

@ -0,0 +1,48 @@
package info.FrostFS.sdk.mappers;
import frostFS.object.Types;
import info.FrostFS.sdk.enums.ObjectType;
import info.FrostFS.sdk.jdo.ContainerId;
import info.FrostFS.sdk.jdo.ObjectAttribute;
import info.FrostFS.sdk.jdo.ObjectHeader;
import org.apache.commons.lang3.EnumUtils;
import static java.util.Objects.isNull;
public class ObjectHeaderMapper {
public static Types.Header toGrpcMessage(ObjectHeader header) {
var objectType = EnumUtils.getEnum(Types.ObjectType.class, header.getObjectType().name());
if (isNull(objectType)) {
throw new IllegalArgumentException(
String.format("Unknown ObjectType. Value: %s.", header.getObjectType().name())
);
}
var head = Types.Header.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(header.getContainerId()))
.setObjectType(objectType);
for (ObjectAttribute objectAttribute : header.getAttributes()) {
head.addAttributes(ObjectAttributeMapper.toGrpcMessage(objectAttribute));
}
return head.build();
}
public static ObjectHeader toModel(Types.Header header) {
var objectType = ObjectType.get(header.getObjectTypeValue());
if (isNull(objectType)) {
throw new IllegalArgumentException(
String.format("Unknown ObjectType. Value: %s.", header.getObjectType())
);
}
return new ObjectHeader(
ContainerId.fromHash(header.getContainerId().getValue().toByteArray()),
objectType,
header.getAttributesList().stream().map(ObjectAttributeMapper::toModel).toArray(ObjectAttribute[]::new),
header.getPayloadLength(),
VersionMapper.toModel(header.getVersion())
);
}
}

View file

@ -0,0 +1,13 @@
package info.FrostFS.sdk.mappers;
import com.google.protobuf.ByteString;
import frostFS.refs.Types;
import info.FrostFS.sdk.jdo.ObjectId;
public class ObjectIdMapper {
public static Types.ObjectID toGrpcMessage(ObjectId objectId) {
return Types.ObjectID.newBuilder()
.setValue(ByteString.copyFrom(objectId.toHash()))
.build();
}
}

View file

@ -0,0 +1,15 @@
package info.FrostFS.sdk.mappers;
import frostFS.object.Types;
import info.FrostFS.sdk.jdo.ObjectFrostFs;
import info.FrostFS.sdk.jdo.ObjectId;
public class ObjectMapper {
public static ObjectFrostFs toModel(Types.Object obj) {
return new ObjectFrostFs(
ObjectHeaderMapper.toModel(obj.getHeader()),
ObjectId.fromHash(obj.getObjectId().getValue().toByteArray()),
obj.getPayload().toByteArray()
);
}
}

View file

@ -0,0 +1,13 @@
package info.FrostFS.sdk.mappers;
import com.google.protobuf.ByteString;
import frostFS.refs.Types;
import info.FrostFS.sdk.jdo.OwnerId;
public class OwnerIdMapper {
public static Types.OwnerID toGrpcMessage(OwnerId ownerId) {
return Types.OwnerID.newBuilder()
.setValue(ByteString.copyFrom(ownerId.toHash()))
.build();
}
}

View file

@ -0,0 +1,23 @@
package info.FrostFS.sdk.mappers;
import frostFS.status.Types;
import info.FrostFS.sdk.enums.StatusCode;
import info.FrostFS.sdk.jdo.Status;
import static java.util.Objects.isNull;
public class StatusMapper {
public static Status toModel(Types.Status status) {
if (isNull(status)) return new Status(StatusCode.SUCCESS);
var statusCode = StatusCode.get(status.getCode());
if (isNull(statusCode)) {
throw new IllegalArgumentException(
String.format("Unknown StatusCode. Value: %s.", status.getCode())
);
}
return new Status(statusCode, status.getMessage());
}
}

View file

@ -0,0 +1,18 @@
package info.FrostFS.sdk.mappers;
import frostFS.refs.Types;
import info.FrostFS.sdk.jdo.Version;
public class VersionMapper {
public static Types.Version toGrpcMessage(Version version) {
return Types.Version.newBuilder()
.setMajor(version.getMajor())
.setMinor(version.getMinor())
.build();
}
public static Version toModel(Types.Version version) {
return new Version(version.getMajor(), version.getMinor());
}
}

View file

@ -0,0 +1,21 @@
package info.FrostFS.sdk.mappers.netmap;
import frostFS.netmap.Service;
import info.FrostFS.sdk.enums.NodeState;
import info.FrostFS.sdk.jdo.netmap.NodeInfo;
import info.FrostFS.sdk.mappers.VersionMapper;
import static java.util.Objects.isNull;
public class NodeInfoMapper {
public static NodeInfo toModel(Service.LocalNodeInfoResponse.Body nodeInfo) {
var nodeState = NodeState.get(nodeInfo.getNodeInfo().getState().getNumber());
if (isNull(nodeState)) {
throw new IllegalArgumentException(
String.format("Unknown NodeState. Value: %s.", nodeInfo.getNodeInfo().getState())
);
}
return new NodeInfo(nodeState, VersionMapper.toModel(nodeInfo.getVersion()));
}
}

View file

@ -0,0 +1,25 @@
package info.FrostFS.sdk.mappers.netmap;
import frostFS.netmap.Types;
import info.FrostFS.sdk.jdo.netmap.PlacementPolicy;
import info.FrostFS.sdk.jdo.netmap.Replica;
public class PlacementPolicyMapper {
public static Types.PlacementPolicy toGrpcMessage(PlacementPolicy placementPolicy) {
var pp = Types.PlacementPolicy.newBuilder()
.setUnique(placementPolicy.isUnique());
for (Replica replica : placementPolicy.getReplicas()) {
pp.addReplicas(ReplicaMapper.toGrpcMessage(replica));
}
return pp.build();
}
public static PlacementPolicy toModel(Types.PlacementPolicy placementPolicy) {
return new PlacementPolicy(
placementPolicy.getUnique(),
placementPolicy.getReplicasList().stream().map(ReplicaMapper::toModel).toArray(Replica[]::new)
);
}
}

View file

@ -0,0 +1,17 @@
package info.FrostFS.sdk.mappers.netmap;
import frostFS.netmap.Types;
import info.FrostFS.sdk.jdo.netmap.Replica;
public class ReplicaMapper {
public static Types.Replica toGrpcMessage(Replica replica) {
return Types.Replica.newBuilder()
.setCount(replica.getCount())
.setSelector(replica.getSelector())
.build();
}
public static Replica toModel(Types.Replica replica) {
return new Replica(replica.getCount(), replica.getSelector());
}
}

23
pom.xml Normal file
View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>FrostFS-sdk-java</artifactId>
<version>0.1.0</version>
<packaging>pom</packaging>
<modules>
<module>protosV2</module>
<module>client</module>
<module>cryptography</module>
<module>modelsV2</module>
</modules>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

106
protosV2/pom.xml Normal file
View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>info.FrostFS.sdk</groupId>
<artifactId>FrostFS-sdk-java</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>protosV2</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<protobuf.version>3.23.0</protobuf.version>
<grpc.version>1.62.2</grpc.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
<version>${grpc.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!-- artifact to download binary protobuf compiler -->
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<!-- make maven using GRPC plugin for compile-custom and
test-compile-custom goals -->
<pluginId>grpc-java</pluginId>
<!-- artifact to download GRPC protobuf compiler plugin -->
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<!-- compile .proto files located under main directory -->
<goal>compile</goal>
<goal>compile-custom</goal>
<!-- compile .proto files located under test directory -->
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,71 @@
syntax = "proto3";
package neo.fs.v2.accounting;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting/grpc;accounting";
option java_package = "frostFS.accounting";
import "accounting/types.proto";
import "refs/types.proto";
import "session/types.proto";
// Accounting service provides methods for interaction with NeoFS sidechain via
// 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.
service AccountingService {
// Returns the amount of funds in GAS token for the requested NeoFS account.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS):
// balance has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON).
rpc Balance(BalanceRequest) returns (BalanceResponse);
}
// BalanceRequest message
message BalanceRequest {
// 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`.
message Body {
// Valid user identifier in `OwnerID` format for which the balance is
// requested. Required field.
neo.fs.v2.refs.OwnerID owner_id = 1;
}
// Body of the balance request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// BalanceResponse message
message BalanceResponse {
// The amount of funds in GAS token for the `OwnerID`'s account requested.
// 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;
}
// Body of the balance response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}

View file

@ -0,0 +1,22 @@
syntax = "proto3";
package neo.fs.v2.accounting;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting/grpc;accounting";
option java_package = "frostFS.accounting";
// Standard floating point data type can't be used in NeoFS due to inexactness
// of the result when doing lots of small number operations. To solve the lost
// precision issue, special `Decimal` format is used for monetary computations.
//
// Please see [The General Decimal Arithmetic
// Specification](http://speleotrove.com/decimal/) for detailed problem
// description.
message Decimal {
// Number in the smallest Token fractions.
int64 value = 1 [ json_name = "value" ];
// Precision value indicating how many smallest fractions can be in one
// integer.
uint32 precision = 2 [ json_name = "precision" ];
}

View file

@ -0,0 +1,227 @@
syntax = "proto3";
package neo.fs.v2.acl;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl/grpc;acl";
option java_package = "frostFS.acl";
import "refs/types.proto";
// Target role of the access control rule in access control list.
enum Role {
// Unspecified role, default value
ROLE_UNSPECIFIED = 0;
// User target rule is applied if sender is the owner of the container
USER = 1;
// 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 neither a user nor a system
// target
OTHERS = 3;
}
// MatchType is an enumeration of match types.
enum MatchType {
// Unspecified match type, default value.
MATCH_TYPE_UNSPECIFIED = 0;
// Return true if strings are equal
STRING_EQUAL = 1;
// Return true if strings are different
STRING_NOT_EQUAL = 2;
}
// Request's operation type to match if the rule is applicable to a particular
// request.
enum Operation {
// Unspecified operation, default value
OPERATION_UNSPECIFIED = 0;
// Get
GET = 1;
// Head
HEAD = 2;
// Put
PUT = 3;
// Delete
DELETE = 4;
// Search
SEARCH = 5;
// GetRange
GETRANGE = 6;
// GetRangeHash
GETRANGEHASH = 7;
}
// Rule execution result action. Either allows or denies access if the rule's
// filters match.
enum Action {
// Unspecified action, default value
ACTION_UNSPECIFIED = 0;
// Allow action
ALLOW = 1;
// Deny action
DENY = 2;
}
// Enumeration of possible sources of Headers to apply filters.
enum HeaderType {
// Unspecified header, default value.
HEADER_UNSPECIFIED = 0;
// Filter request headers
REQUEST = 1;
// Filter object headers
OBJECT = 2;
// Filter service headers. These are not processed by NeoFS nodes and
// exist for service use only.
SERVICE = 3;
}
// Describes a single eACL rule.
message EACLRecord {
// NeoFS request Verb to match
Operation operation = 1 [ json_name = "operation" ];
// 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 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:`
// prefix to the name. Here is the list of fields available via this prefix:
//
// * $Object:version \
// version
// * $Object:objectID \
// object_id
// * $Object:containerID \
// container_id
// * $Object:ownerID \
// owner_id
// * $Object:creationEpoch \
// creation_epoch
// * $Object:payloadLength \
// payload_length
// * $Object:payloadHash \
// payload_hash
// * $Object:objectType \
// object_type
// * $Object:homomorphicHash \
// homomorphic_hash
//
// Please note, that if request or response does not have object's headers of
// full object (Range, RangeHash, Search, Delete), it will not be possible to
// filter by object header fields or user attributes. From the well-known list
// only `$Object:objectID` and `$Object:containerID` will be available, as
// it's possible to take that information from the requested address.
message Filter {
// Define if Object or Request header will be used
HeaderType header_type = 1 [ json_name = "headerType" ];
// Match operation type
MatchType match_type = 2 [ json_name = "matchType" ];
// Name of the Header to use
string key = 3 [ json_name = "key" ];
// Expected Header Value or pattern to match
string value = 4 [ json_name = "value" ];
}
// List of filters to match and see if rule is applicable
repeated Filter filters = 3 [ json_name = "filters" ];
// Target to apply ACL rule. Can be a subject's role class or a list of public
// keys to match.
message Target {
// Target subject's role class
Role role = 1 [ json_name = "role" ];
// List of public keys to identify target subject
repeated bytes keys = 2 [ json_name = "keys" ];
}
// List of target subjects to apply ACL rule to
repeated Target targets = 4 [ json_name = "targets" ];
}
// 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 section for detailed description.
message EACLTable {
// eACL format version. Effectively, the version of API library used to create
// eACL Table.
neo.fs.v2.refs.Version version = 1 [ json_name = "version" ];
// Identifier of the container that should use given access control rules
neo.fs.v2.refs.ContainerID container_id = 2 [ json_name = "containerID" ];
// List of Extended ACL rules
repeated EACLRecord records = 3 [ json_name = "records" ];
}
// BearerToken allows to attach signed Extended ACL rules to the request in
// `RequestMetaHeader`. If container's Basic ACL rules allow, the attached rule
// set will be checked instead of one attached to the container itself. Just
// like [JWT](https://jwt.io), it has a limited lifetime and scope, hence can be
// used in the similar use cases, like providing authorisation to externally
// authenticated party.
//
// 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 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
// valid for this specific container. Otherwise, any container of the same
// owner is allowed.
EACLTable eacl_table = 1 [ json_name = "eaclTable" ];
// `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" ];
// Lifetime parameters of the token. Field names taken from
// [rfc7519](https://tools.ietf.org/html/rfc7519).
message TokenLifetime {
// Expiration Epoch
uint64 exp = 1 [ json_name = "exp" ];
// Not valid before Epoch
uint64 nbf = 2 [ json_name = "nbf" ];
// Issued at Epoch
uint64 iat = 3 [ json_name = "iat" ];
}
// Token expiration and valid time period parameters
TokenLifetime lifetime = 3 [ json_name = "lifetime" ];
// AllowImpersonate flag to consider token signer as request owner.
// If this field is true extended ACL table in token body isn't processed.
bool allow_impersonate = 4 [ json_name = "allowImpersonate" ];
}
// Bearer Token body
Body body = 1 [ json_name = "body" ];
// Signature of BearerToken body
neo.fs.v2.refs.Signature signature = 2 [ json_name = "signature" ];
}

View file

@ -0,0 +1,172 @@
syntax = "proto3";
package frostfs.v2.apemanager;
import "apemanager/types.proto";
import "session/types.proto";
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager/grpc;apemanager";
option java_package = "frostFS.apemanager";
// `APEManagerService` provides API to manage rule chains within sidechain's
// `Policy` smart contract.
service APEManagerService {
// Add a rule chain for a specific target to `Policy` smart contract.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// the chain has been successfully added;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// container (as target) not found;
// - **APE_MANAGER_ACCESS_DENIED** (5120, SECTION_APE_MANAGER): \
// the operation is denied by the service.
rpc AddChain(AddChainRequest) returns (AddChainResponse);
// Remove a rule chain for a specific target from `Policy` smart contract.
// RemoveChain is an idempotent operation: removal of non-existing rule chain
// also means success.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// the chain has been successfully removed;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// container (as target) not found;
// - **APE_MANAGER_ACCESS_DENIED** (5120, SECTION_APE_MANAGER): \
// the operation is denied by the service.
rpc RemoveChain(RemoveChainRequest) returns (RemoveChainResponse);
// List chains defined for a specific target from `Policy` smart contract.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// chains have been successfully listed;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// container (as target) not found;
// - **APE_MANAGER_ACCESS_DENIED** (5120, SECTION_APE_MANAGER): \
// the operation is denied by the service.
rpc ListChains(ListChainsRequest) returns (ListChainsResponse);
}
message AddChainRequest {
message Body {
// A target for which a rule chain is added.
ChainTarget target = 1;
// The chain to set for the target.
Chain chain = 2;
}
// The request's body.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
message AddChainResponse {
message Body {
// Chain ID assigned for the added rule chain.
// If chain ID is left empty in the request, then
// it will be generated.
bytes chain_id = 1;
}
// The response's body.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
message RemoveChainRequest {
message Body {
// Target for which a rule chain is removed.
ChainTarget target = 1;
// Chain ID assigned for the rule chain.
bytes chain_id = 2;
}
// The request's body.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
message RemoveChainResponse {
// Since RemoveChain is an idempotent operation, then the only indicator that
// operation could not be performed is an error returning to a client.
message Body {}
// The response's body.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
message ListChainsRequest {
message Body {
// Target for which rule chains are listed.
ChainTarget target = 1;
}
// The request's body.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
message ListChainsResponse {
message Body {
// The list of chains defined for the reqeusted target.
repeated Chain chains = 1;
}
// The response's body.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}

View file

@ -0,0 +1,34 @@
syntax = "proto3";
package frostfs.v2.apemanager;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager/grpc;apemanager";
option java_package = "frostFS.apemanager";
// TargetType is a type target to which a rule chain is defined.
enum TargetType {
UNDEFINED = 0;
NAMESPACE = 1;
CONTAINER = 2;
USER = 3;
GROUP = 4;
}
// ChainTarget is an object to which a rule chain is defined.
message ChainTarget {
TargetType type = 1;
string name = 2;
}
// Chain is a chain of rules defined for a specific target.
message Chain {
oneof kind {
// Raw representation of a serizalized rule chain.
bytes raw = 1;
}
}

View file

@ -0,0 +1,431 @@
syntax = "proto3";
package neo.fs.v2.container;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container/grpc;container";
option java_package = "frostFS.container";
import "acl/types.proto";
import "container/types.proto";
import "refs/types.proto";
import "session/types.proto";
// `ContainerService` provides API to interact with `Container` smart contract
// in NeoFS sidechain via other NeoFS nodes. All of those actions can be done
// equivalently by directly issuing transactions and RPC calls to sidechain
// nodes.
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, the
// container is added into smart contract storage.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// request to save the container has been sent to the sidechain;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// container create access denied.
rpc Put(PutRequest) returns (PutResponse);
// `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, the
// container is added into smart contract storage.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// request to remove the container has been sent to the sidechain;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// container delete access denied.
rpc Delete(DeleteRequest) returns (DeleteResponse);
// Returns container structure from `Container` smart contract storage.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// container has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// requested container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied.
rpc Get(GetRequest) returns (GetResponse);
// Returns all owner's containers from 'Container` smart contract' storage.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// container list has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// container list access denied.
rpc List(ListRequest) returns (ListResponse);
// Invokes 'SetEACL' method of 'Container` smart contract and returns response
// immediately. After one more block in sidechain, changes in an Extended ACL
// are added into smart contract storage.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// request to save container eACL has been sent to the sidechain;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// set container eACL access denied.
rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse);
// Returns Extended ACL table and signature from `Container` smart contract
// storage.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// container eACL has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON);
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// container not found;
// - **EACL_NOT_FOUND** (3073, SECTION_CONTAINER): \
// eACL table not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container eACL is denied.
rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse);
// Announces the space values used by the container for P2P synchronization.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// estimation of used space has been successfully announced;
// - Common failures (SECTION_FAILURE_COMMON).
rpc AnnounceUsedSpace(AnnounceUsedSpaceRequest)
returns (AnnounceUsedSpaceResponse);
}
// New NeoFS Container creation request
message PutRequest {
// Container creation request has container structure's signature as a
// separate field. It's not stored in sidechain, just verified on container
// creation by `Container` smart contract. `ContainerID` is a SHA256 hash of
// the stable-marshalled container strucutre, hence there is no need for
// additional signature checks.
message Body {
// Container structure to register in NeoFS
container.Container container = 1;
// Signature of a stable-marshalled container according to RFC-6979.
neo.fs.v2.refs.SignatureRFC6979 signature = 2;
}
// Body of container put request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// New NeoFS Container creation response
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 has been done as expected.
message Body {
// Unique identifier of the newly created container
neo.fs.v2.refs.ContainerID container_id = 1;
}
// Body of container put response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Container removal request
message DeleteRequest {
// 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
neo.fs.v2.refs.ContainerID container_id = 1;
// `ContainerID` signed with the container owner's key according to
// RFC-6979.
neo.fs.v2.refs.SignatureRFC6979 signature = 2;
}
// Body of container delete request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// `DeleteResponse` has an empty body because delete operation is asynchronous
// and done via consensus in Inner Ring nodes.
message DeleteResponse {
// `DeleteResponse` has an empty body because delete operation is asynchronous
// and done via consensus in Inner Ring nodes.
message Body {}
// Body of container delete response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Get container structure
message GetRequest {
// Get container structure request body.
message Body {
// Identifier of the container to get
neo.fs.v2.refs.ContainerID container_id = 1;
}
// Body of container get request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Get container structure
message GetResponse {
// Get container response body does not have container structure signature. It
// has been already verified upon container creation.
message Body {
// Requested container structure
Container container = 1;
// Signature of a stable-marshalled container according to RFC-6979.
neo.fs.v2.refs.SignatureRFC6979 signature = 2;
// 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.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// List containers
message ListRequest {
// List containers request body.
message Body {
// Identifier of the container owner
neo.fs.v2.refs.OwnerID owner_id = 1;
}
// Body of list containers request message
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// List containers
message ListResponse {
// List containers response body.
message Body {
// List of `ContainerID`s belonging to the requested `OwnerID`
repeated refs.ContainerID container_ids = 1;
}
// Body of list containers response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Set Extended ACL
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 the container
neo.fs.v2.acl.EACLTable eacl = 1;
// Signature of stable-marshalled Extended ACL table according to RFC-6979.
neo.fs.v2.refs.SignatureRFC6979 signature = 2;
}
// Body of set extended acl request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Set Extended ACL
message SetExtendedACLResponse {
// `SetExtendedACLResponse` has an empty body because the operation is
// asynchronous and the update should be reflected in `Container` smart
// contract's storage after next block is issued in sidechain.
message Body {}
// Body of set extended acl response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Get Extended ACL
message GetExtendedACLRequest {
// Get Extended ACL request body
message Body {
// Identifier of the container having Extended ACL
neo.fs.v2.refs.ContainerID container_id = 1;
}
// Body of get extended acl request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Get Extended ACL
message GetExtendedACLResponse {
// 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;
// Signature of stable-marshalled Extended ACL according to RFC-6979.
neo.fs.v2.refs.SignatureRFC6979 signature = 2;
// Session token if Extended ACL was set within a session
neo.fs.v2.session.SessionToken session_token = 3;
}
// Body of get extended acl response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Announce container used space
message AnnounceUsedSpaceRequest {
// Container used space announcement body.
message Body {
// Announcement contains used space information for a single container.
message Announcement {
// 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 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,
// announcements are transferred in a batch.
repeated Announcement announcements = 1;
}
// Body of announce used space request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Announce container used space
message AnnounceUsedSpaceResponse {
// `AnnounceUsedSpaceResponse` has an empty body because announcements are
// one way communication.
message Body {}
// Body of announce used space response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}

View file

@ -0,0 +1,76 @@
syntax = "proto3";
package neo.fs.v2.container;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container/grpc;container";
option java_package = "frostFS.container";
import "netmap/types.proto";
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. 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 the container.
neo.fs.v2.refs.Version version = 1 [ json_name = "version" ];
// Identifier of the container owner
neo.fs.v2.refs.OwnerID owner_id = 2 [ json_name = "ownerID" ];
// 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 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 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
// values will be considered invalid.
//
// There are some "well-known" attributes affecting system behaviour:
//
// * [ __SYSTEM__NAME ] \
// (`__NEOFS__NAME` is deprecated) \
// String of a human-friendly container name registered as a domain in
// NNS contract.
// * [ __SYSTEM__ZONE ] \
// (`__NEOFS__ZONE` is deprecated) \
// String of a zone for `__SYSTEM__NAME` (`__NEOFS__NAME` is deprecated).
// Used as a TLD of a domain name in NNS contract. If no zone is specified,
// use default zone: `container`.
// * [ __SYSTEM__DISABLE_HOMOMORPHIC_HASHING ] \
// (`__NEOFS__DISABLE_HOMOMORPHIC_HASHING` is deprecated) \
// Disables homomorphic hashing for the container if the value equals "true"
// string. Any other values are interpreted as missing attribute. Container
// could be accepted in a NeoFS network only if the global network hashing
// configuration value corresponds with that attribute's value. After
// container inclusion, network setting is ignored.
//
// And some well-known attributes used by applications only:
//
// * Name \
// Human-friendly name
// * Timestamp \
// User-defined local time of container creation in Unix Timestamp format
message Attribute {
// Attribute name key
string key = 1 [ json_name = "key" ];
// Attribute value
string value = 2 [ json_name = "value" ];
}
// Attributes represent immutable container's meta data
repeated Attribute attributes = 5 [ json_name = "attributes" ];
// Placement policy for the object inside the container
neo.fs.v2.netmap.PlacementPolicy placement_policy = 6
[ json_name = "placementPolicy" ];
}

View file

@ -0,0 +1,19 @@
syntax = "proto3";
package neo.fs.v2.lock;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/lock/grpc;lock";
option java_package = "frostFS.lock";
import "refs/types.proto";
// Lock objects protects a list of objects from being deleted. The lifetime of a
// lock object is limited similar to regular objects in
// `__SYSTEM__EXPIRATION_EPOCH` (`__NEOFS__EXPIRATION_EPOCH` is deprecated)
// attribute. Lock object MUST have expiration epoch. It is impossible to delete
// a lock object via ObjectService.Delete RPC call.
message Lock {
// List of objects to lock. Must not be empty or carry empty IDs.
// All members must be of the `REGULAR` type.
repeated neo.fs.v2.refs.ObjectID members = 1 [ json_name = "members" ];
}

View file

@ -0,0 +1,162 @@
syntax = "proto3";
package neo.fs.v2.netmap;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap/grpc;netmap";
option java_package = "frostFS.netmap";
import "netmap/types.proto";
import "refs/types.proto";
import "session/types.proto";
// `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. 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):
// information about the server has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON).
rpc LocalNodeInfo(LocalNodeInfoRequest) returns (LocalNodeInfoResponse);
// Read recent information about the NeoFS network.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS):
// information about the current network state has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON).
rpc NetworkInfo(NetworkInfoRequest) returns (NetworkInfoResponse);
// Returns network map snapshot of the current NeoFS epoch.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS):
// information about the current network map has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON).
rpc NetmapSnapshot(NetmapSnapshotRequest) returns (NetmapSnapshotResponse);
}
// Get NodeInfo structure directly from a particular node
message LocalNodeInfoRequest {
// LocalNodeInfo request body is empty.
message Body {}
// Body of the LocalNodeInfo request message
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Local Node Info, including API Version in use
message LocalNodeInfoResponse {
// Local Node Info, including API Version in use.
message Body {
// Latest NeoFS API version in use
neo.fs.v2.refs.Version version = 1;
// NodeInfo structure with recent information from node itself
NodeInfo node_info = 2;
}
// Body of the balance response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect response execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Get NetworkInfo structure with the network view from a particular node.
message NetworkInfoRequest {
// NetworkInfo request body is empty.
message Body {}
// Body of the NetworkInfo request message
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Response with NetworkInfo structure including current epoch and
// sidechain magic number.
message NetworkInfoResponse {
// Information about the network.
message Body {
// NetworkInfo structure with recent information.
NetworkInfo network_info = 1;
}
// Body of the NetworkInfo response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect response execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Get netmap snapshot request
message NetmapSnapshotRequest {
// Get netmap snapshot request body.
message Body {}
// Body of get netmap snapshot request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Response with current netmap snapshot
message NetmapSnapshotResponse {
// Get netmap snapshot response body
message Body {
// Structure of the requested network map.
Netmap netmap = 1 [ json_name = "netmap" ];
}
// Body of get netmap snapshot response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect response execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}

View file

@ -0,0 +1,323 @@
syntax = "proto3";
package neo.fs.v2.netmap;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap/grpc;netmap";
option java_package = "frostFS.netmap";
// Operations on filters
enum Operation {
// No Operation defined
OPERATION_UNSPECIFIED = 0;
// Equal
EQ = 1;
// Not Equal
NE = 2;
// Greater then
GT = 3;
// Greater or equal
GE = 4;
// Less then
LT = 5;
// Less or equal
LE = 6;
// Logical OR
OR = 7;
// Logical AND
AND = 8;
// Logical negation
NOT = 9;
}
// Selector modifier shows how the node set will be formed. By default selector
// just groups nodes into a bucket by attribute, selecting nodes only by their
// hash distance.
enum Clause {
// 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
SAME = 1;
// DISTINCT will select nodes having different values of bucket attribute
DISTINCT = 2;
}
// 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 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
string name = 1 [ json_name = "name" ];
// Key to filter
string key = 2 [ json_name = "key" ];
// Filtering operation
Operation op = 3 [ json_name = "op" ];
// Value to match
string value = 4 [ json_name = "value" ];
// List of inner filters. Top level operation will be applied to the whole
// list.
repeated Filter filters = 5 [ json_name = "filters" ];
}
// Selector chooses a number of nodes from the bucket taking the nearest nodes
// to the provided `ContainerID` by hash distance.
message Selector {
// Selector name to reference in object placement section
string name = 1 [ json_name = "name" ];
// How many nodes to select from the bucket
uint32 count = 2 [ json_name = "count" ];
// Selector modifier showing how to form a bucket
Clause clause = 3 [ json_name = "clause" ];
// Bucket attribute to select from
string attribute = 4 [ json_name = "attribute" ];
// Filter reference to select from
string filter = 5 [ json_name = "filter" ];
}
// 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
// default.
message Replica {
// How many object replicas to put
uint32 count = 1 [ json_name = "count" ];
// Named selector bucket to put replicas
string selector = 2 [ json_name = "selector" ];
// Data shards count
uint32 ec_data_count = 3 [ json_name = "ecDataCount" ];
// Parity shards count
uint32 ec_parity_count = 4 [ json_name = "ecParityCount" ];
}
// Set of rules to select a subset of nodes from `NetworkMap` able to store
// container's objects. The format is simple enough to transpile from different
// storage policy definition languages.
message PlacementPolicy {
// Rules to set number of object replicas and place each one into a named
// bucket
repeated Replica replicas = 1 [ json_name = "replicas" ];
// Container backup factor controls how deep NeoFS will search for nodes
// alternatives to include into container's nodes subset
uint32 container_backup_factor = 2 [ json_name = "containerBackupFactor" ];
// Set of Selectors to form the container's nodes subset
repeated Selector selectors = 3 [ json_name = "selectors" ];
// List of named filters to reference in selectors
repeated Filter filters = 4 [ json_name = "filters" ];
// Unique flag defines non-overlapping application for replicas
bool unique = 5 [ json_name = "unique" ];
}
// NeoFS node description
message NodeInfo {
// Public key of the NeoFS node in a binary format
bytes public_key = 1 [ json_name = "publicKey" ];
// Ways to connect to a node
repeated string addresses = 2 [ json_name = "addresses" ];
// Administrator-defined Attributes of the NeoFS Storage Node.
//
// `Attribute` is a Key-Value metadata pair. Key name must be a valid UTF-8
// string. Value can't be empty.
//
// Attributes can be constructed into a chain of attributes: any attribute can
// have a parent attribute and a child attribute (except the first and the
// last one). A string representation of the chain of attributes in NeoFS
// Storage Node configuration uses ":" and "/" symbols, e.g.:
//
// `NEOFS_NODE_ATTRIBUTE_1=key1:val1/key2:val2`
//
// Therefore the string attribute representation in the Node configuration
// must use "\:", "\/" and "\\" escaped symbols if any of them appears in an
// attribute's key or value.
//
// Node's attributes are mostly used during Storage Policy evaluation to
// calculate object's placement and find a set of nodes satisfying policy
// requirements. There are some "well-known" node attributes common to all the
// Storage Nodes in the network and used implicitly with default values if not
// explicitly set:
//
// * Capacity \
// Total available disk space in Gigabytes.
// * Price \
// Price in GAS tokens for storing one GB of data during one Epoch. In node
// attributes it's a string presenting floating point number with comma or
// point delimiter for decimal part. In the Network Map it will be saved as
// 64-bit unsigned integer representing number of minimal token fractions.
// * UN-LOCODE \
// Node's geographic location in
// [UN/LOCODE](https://www.unece.org/cefact/codesfortrade/codes_index.html)
// format approximated to the nearest point defined in the standard.
// * CountryCode \
// Country code in
// [ISO 3166-1_alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
// format. Calculated automatically from `UN-LOCODE` attribute.
// * Country \
// Country short name in English, as defined in
// [ISO-3166](https://www.iso.org/obp/ui/#search). Calculated automatically
// from `UN-LOCODE` attribute.
// * Location \
// Place names are given, whenever possible, in their national language
// versions as expressed in the Roman alphabet using the 26 characters of
// the character set adopted for international trade data interchange,
// written without diacritics . Calculated automatically from `UN-LOCODE`
// attribute.
// * SubDivCode \
// Country's administrative subdivision where node is located. Calculated
// automatically from `UN-LOCODE` attribute based on `SubDiv` field.
// Presented in [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2)
// format.
// * SubDiv \
// Country's administrative subdivision name, as defined in
// [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2). Calculated
// automatically from `UN-LOCODE` attribute.
// * Continent \
// Node's continent name according to the [Seven-Continent model]
// (https://en.wikipedia.org/wiki/Continent#Number). Calculated
// automatically from `UN-LOCODE` attribute.
// * ExternalAddr
// Node's preferred way for communications with external clients.
// Clients SHOULD use these addresses if possible.
// Must contain a comma-separated list of multi-addresses.
//
// For detailed description of each well-known attribute please see the
// corresponding section in NeoFS Technical Specification.
message Attribute {
// Key of the node attribute
string key = 1 [ json_name = "key" ];
// Value of the node attribute
string value = 2 [ json_name = "value" ];
// Parent keys, if any. For example for `City` it could be `Region` and
// `Country`.
repeated string parents = 3 [ json_name = "parents" ];
}
// Carries list of the NeoFS node attributes in a key-value form. Key name
// must be a node-unique valid UTF-8 string. Value can't be empty. NodeInfo
// structures with duplicated attribute names or attributes with empty values
// will be considered invalid.
repeated Attribute attributes = 3 [ json_name = "attributes" ];
// Represents the enumeration of various states of the NeoFS node.
enum State {
// Unknown state
UNSPECIFIED = 0;
// Active state in the network
ONLINE = 1;
// Network unavailable state
OFFLINE = 2;
// Maintenance state
MAINTENANCE = 3;
}
// Carries state of the NeoFS node
State state = 4 [ json_name = "state" ];
}
// Network map structure
message Netmap {
// Network map revision number.
uint64 epoch = 1 [ json_name = "epoch" ];
// Nodes presented in network.
repeated NodeInfo nodes = 2 [ json_name = "nodes" ];
}
// NeoFS network configuration
message NetworkConfig {
// Single configuration parameter. Key MUST be network-unique.
//
// System parameters:
// - **AuditFee** \
// Fee paid by the storage group owner to the Inner Ring member.
// Value: little-endian integer. Default: 0.
// - **BasicIncomeRate** \
// Cost of storing one gigabyte of data for a period of one epoch. Paid by
// container owner to container nodes.
// Value: little-endian integer. Default: 0.
// - **ContainerAliasFee** \
// Fee paid for named container's creation by the container owner.
// Value: little-endian integer. Default: 0.
// - **ContainerFee** \
// Fee paid for container creation by the container owner.
// Value: little-endian integer. Default: 0.
// - **EpochDuration** \
// NeoFS epoch duration measured in Sidechain blocks.
// Value: little-endian integer. Default: 0.
// - **HomomorphicHashingDisabled** \
// Flag of disabling the homomorphic hashing of objects' payload.
// Value: true if any byte != 0. Default: false.
// - **InnerRingCandidateFee** \
// Fee for entrance to the Inner Ring paid by the candidate.
// Value: little-endian integer. Default: 0.
// - **MaintenanceModeAllowed** \
// Flag allowing setting the MAINTENANCE state to storage nodes.
// Value: true if any byte != 0. Default: false.
// - **MaxObjectSize** \
// Maximum size of physically stored NeoFS object measured in bytes.
// Value: little-endian integer. Default: 0.
// - **WithdrawFee** \
// Fee paid for withdrawal of funds paid by the account owner.
// Value: little-endian integer. Default: 0.
// - **MaxECDataCount** \
// Maximum number of data shards for EC placement policy.
// Value: little-endian integer. Default: 0.
// - **MaxECParityCount** \
// Maximum number of parity shards for EC placement policy.
// Value: little-endian integer. Default: 0.
message Parameter {
// Parameter key. UTF-8 encoded string
bytes key = 1 [ json_name = "key" ];
// Parameter value
bytes value = 2 [ json_name = "value" ];
}
// List of parameter values
repeated Parameter parameters = 1 [ json_name = "parameters" ];
}
// Information about NeoFS network
message NetworkInfo {
// Number of the current epoch in the NeoFS network
uint64 current_epoch = 1 [ json_name = "currentEpoch" ];
// Magic number of the sidechain of the NeoFS network
uint64 magic_number = 2 [ json_name = "magicNumber" ];
// MillisecondsPerBlock network parameter of the sidechain of the NeoFS
// network
int64 ms_per_block = 3 [ json_name = "msPerBlock" ];
// NeoFS network configuration
NetworkConfig network_config = 4 [ json_name = "networkConfig" ];
}

View file

@ -0,0 +1,816 @@
syntax = "proto3";
package neo.fs.v2.object;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object/grpc;object";
option java_package = "frostFS.object";
import "object/types.proto";
import "refs/types.proto";
import "session/types.proto";
// `ObjectService` provides API for manipulating objects. Object operations do
// 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 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. The requested
// object can be restored by concatenation of object message payload and all
// chunks keeping the receiving order.
//
// Extended headers can change `Get` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requsted version of Network Map for object placement
// calculation.
// * [ __SYSTEM__NETMAP_LOOKUP_DEPTH ] \
// (`__NEOFS__NETMAP_LOOKUP_DEPTH` is deprecated) \
// Will try older versions (starting from `__SYSTEM__NETMAP_EPOCH`
// (`__NEOFS__NETMAP_EPOCH` is deprecated) if specified or the latest one
// otherwise) of Network Map to find an object until the depth limit is
// reached.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// object has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// read access to the object is denied;
// - **OBJECT_NOT_FOUND** (2049, SECTION_OBJECT): \
// object not found in container;
// - **OBJECT_ALREADY_REMOVED** (2052, SECTION_OBJECT): \
// the requested object has been marked as deleted;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc Get(GetRequest) returns (stream GetResponse);
// Put the object into container. Request uses gRPC stream. First message
// SHOULD be of PutHeader type. `ContainerID` and `OwnerID` of an object
// 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 the direct order of fragmentation.
//
// Extended headers can change `Put` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requsted version of Network Map for object placement
// calculation.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// object has been successfully saved in the container;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// write access to the container is denied;
// - **LOCKED** (2050, SECTION_OBJECT): \
// placement of an object of type TOMBSTONE that includes at least one
// locked object is prohibited;
// - **LOCK_NON_REGULAR_OBJECT** (2051, SECTION_OBJECT): \
// placement of an object of type LOCK that includes at least one object of
// type other than REGULAR is prohibited;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object storage container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_NOT_FOUND** (4096, SECTION_SESSION): \
// (for trusted object preparation) session private key does not exist or
// has
// been deleted;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc Put(stream PutRequest) returns (PutResponse);
// Delete the object from a container. There is no immediate removal
// guarantee. Object will be marked for removal and deleted eventually.
//
// Extended headers can change `Delete` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requested version of Network Map for object placement
// calculation.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// object has been successfully marked to be removed from the container;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// delete access to the object is denied;
// - **OBJECT_NOT_FOUND** (2049, SECTION_OBJECT): \
// the object could not be deleted because it has not been \
// found within the container;
// - **LOCKED** (2050, SECTION_OBJECT): \
// deleting a locked object is prohibited;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc Delete(DeleteRequest) returns (DeleteResponse);
// 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 will be returned instead.
//
// Extended headers can change `Head` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requested version of Network Map for object placement
// calculation.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// object header has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// access to operation HEAD of the object is denied;
// - **OBJECT_NOT_FOUND** (2049, SECTION_OBJECT): \
// object not found in container;
// - **OBJECT_ALREADY_REMOVED** (2052, SECTION_OBJECT): \
// the requested object has been marked as deleted;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc Head(HeadRequest) returns (HeadResponse);
// Search objects in container. Search query allows to match by Object
// Header's filed values. Please see the corresponding NeoFS Technical
// Specification section for more details.
//
// Extended headers can change `Search` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requested version of Network Map for object placement
// calculation.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// objects have been successfully selected;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// access to operation SEARCH of the object is denied;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// search container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc Search(SearchRequest) returns (stream SearchResponse);
// 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 the
// receiving order.
//
// Extended headers can change `GetRange` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requested version of Network Map for object placement
// calculation.
// * [ __SYSTEM__NETMAP_LOOKUP_DEPTH ] \
// (`__NEOFS__NETMAP_LOOKUP_DEPTH` is deprecated) \
// Will try older versions of Network Map to find an object until the depth
// limit is reached.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// data range of the object payload has been successfully read;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// access to operation RANGE of the object is denied;
// - **OBJECT_NOT_FOUND** (2049, SECTION_OBJECT): \
// object not found in container;
// - **OBJECT_ALREADY_REMOVED** (2052, SECTION_OBJECT): \
// the requested object has been marked as deleted.
// - **OUT_OF_RANGE** (2053, SECTION_OBJECT): \
// the requested range is out of bounds;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc GetRange(GetRangeRequest) returns (stream GetRangeResponse);
// 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 the ranges order in
// the request. Note that hash is calculated for XORed data.
//
// Extended headers can change `GetRangeHash` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requested version of Network Map for object placement
// calculation.
// * [ __SYSTEM__NETMAP_LOOKUP_DEPTH ] \
// (`__NEOFS__NETMAP_LOOKUP_DEPTH` is deprecated) \
// Will try older versions of Network Map to find an object until the depth
// limit is reached.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// data range of the object payload has been successfully hashed;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// access to operation RANGEHASH of the object is denied;
// - **OBJECT_NOT_FOUND** (2049, SECTION_OBJECT): \
// object not found in container;
// - **OUT_OF_RANGE** (2053, SECTION_OBJECT): \
// the requested range is out of bounds;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc GetRangeHash(GetRangeHashRequest) returns (GetRangeHashResponse);
// Put the prepared object into container.
// `ContainerID`, `ObjectID`, `OwnerID`, `PayloadHash` and `PayloadLength` of
// an object MUST be set.
//
// Extended headers can change `Put` behaviour:
// * [ __SYSTEM__NETMAP_EPOCH \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Will use the requested version of Network Map for object placement
// calculation.
//
// Please refer to detailed `XHeader` description.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS): \
// object has been successfully saved in the container;
// - Common failures (SECTION_FAILURE_COMMON);
// - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
// write access to the container is denied;
// - **LOCKED** (2050, SECTION_OBJECT): \
// placement of an object of type TOMBSTONE that includes at least one
// locked object is prohibited;
// - **LOCK_NON_REGULAR_OBJECT** (2051, SECTION_OBJECT): \
// placement of an object of type LOCK that includes at least one object of
// type other than REGULAR is prohibited;
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// object storage container not found;
// - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \
// access to container is denied;
// - **TOKEN_NOT_FOUND** (4096, SECTION_SESSION): \
// (for trusted object preparation) session private key does not exist or
// has
// been deleted;
// - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \
// provided session token has expired.
rpc PutSingle(PutSingleRequest) returns (PutSingleResponse);
}
// GET object request
message GetRequest {
// GET Object request body
message Body {
// Address of the requested object
neo.fs.v2.refs.Address address = 1;
// If `raw` flag is set, request will work only with objects that are
// physically stored on the peer node
bool raw = 2;
}
// Body of get object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// GET object response
message GetResponse {
// GET Object Response body
message Body {
// Initial part of the `Object` structure stream. Technically it's a
// set of all `Object` structure's fields except `payload`.
message Init {
// Object's unique identifier.
neo.fs.v2.refs.ObjectID object_id = 1;
// Signed `ObjectID`
neo.fs.v2.refs.Signature signature = 2;
// Object metadata headers
Header header = 3;
}
// Single message in the response stream.
oneof object_part {
// Initial part of the object stream
Init init = 1;
// Chunked object payload
bytes chunk = 2;
// Meta information of split hierarchy for object assembly.
SplitInfo split_info = 3;
// Meta information for EC object assembly.
ECInfo ec_info = 4;
}
}
// Body of get object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// PUT object request
message PutRequest {
// PUT request body
message Body {
// Newly created object structure parameters. If some optional parameters
// are not set, they will be calculated by a peer node.
message Init {
// ObjectID if available.
neo.fs.v2.refs.ObjectID object_id = 1;
// Object signature if available
neo.fs.v2.refs.Signature signature = 2;
// Object's Header
Header header = 3;
// Number of copies of the object to store within the RPC call. By
// default, object is processed according to the container's placement
// policy. Can be one of:
// 1. A single number; applied to the whole request and is treated as
// a minimal number of nodes that must store an object to complete the
// request successfully.
// 2. An ordered array; every number is treated as a minimal number of
// nodes in a corresponding placement vector that must store an object
// to complete the request successfully. The length MUST equal the
// placement vectors number, otherwise request is considered malformed.
repeated uint32 copies_number = 4;
}
// Single message in the request stream.
oneof object_part {
// Initial part of the object stream
Init init = 1;
// Chunked object payload
bytes chunk = 2;
}
}
// Body of put object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// PUT Object response
message PutResponse {
// PUT Object response body
message Body {
// Identifier of the saved object
neo.fs.v2.refs.ObjectID object_id = 1;
}
// Body of put object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Object DELETE request
message DeleteRequest {
// Object DELETE request body
message Body {
// Address of the object to be deleted
neo.fs.v2.refs.Address address = 1;
}
// Body of delete object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// DeleteResponse body is empty because we cannot guarantee permanent object
// removal in distributed system.
message DeleteResponse {
// Object DELETE Response has an empty body.
message Body {
// Address of the tombstone created for the deleted object
neo.fs.v2.refs.Address tombstone = 1;
}
// Body of delete object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Object HEAD request
message HeadRequest {
// Object HEAD request body
message Body {
// Address of the object with the requested Header
neo.fs.v2.refs.Address address = 1;
// Return only minimal header subset
bool main_only = 2;
// If `raw` flag is set, request will work only with objects that are
// physically stored on the peer node
bool raw = 3;
}
// Body of head object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// 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 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
Header header = 1 [ json_name = "header" ];
// Signed `ObjectID` to verify full header's authenticity
neo.fs.v2.refs.Signature signature = 2 [ json_name = "signature" ];
}
// Object HEAD response
message HeadResponse {
// Object HEAD response body
message Body {
// Requested object header, it's part or meta information about split
// object.
oneof head {
// Full object's `Header` with `ObjectID` signature
HeaderWithSignature header = 1;
// Short object header
ShortHeader short_header = 2;
// Meta information of split hierarchy.
SplitInfo split_info = 3;
// Meta information for EC object assembly.
ECInfo ec_info = 4;
}
}
// Body of head object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Object Search request
message SearchRequest {
// Object Search request body
message Body {
// Container identifier were to search
neo.fs.v2.refs.ContainerID container_id = 1;
// Version of the Query Language used
uint32 version = 2;
// Filter structure checks if the object header field or the attribute
// content matches a value.
//
// If no filters are set, search request will return all objects of the
// container, including Regular object and Tombstone
// objects. Most human users expect to get only object they can directly
// 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:`
// prefix to the name. Here is the list of fields available via this prefix:
//
// * $Object:version \
// version
// * $Object:objectID \
// object_id
// * $Object:containerID \
// container_id
// * $Object:ownerID \
// owner_id
// * $Object:creationEpoch \
// creation_epoch
// * $Object:payloadLength \
// payload_length
// * $Object:payloadHash \
// payload_hash
// * $Object:objectType \
// object_type
// * $Object:homomorphicHash \
// homomorphic_hash
// * $Object:split.parent \
// object_id of parent
// * $Object:split.splitID \
// 16 byte UUIDv4 used to identify the split object hierarchy parts
//
// There are some well-known filter aliases to match objects by certain
// properties:
//
// * $Object:ROOT \
// 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 a separate top-level root object. Objects of other types like
// Locks 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
// value and matcher type.
// * $Object:PHY \
// Returns only objects physically stored in the system. This filter is
// activated if the `key` exists, disregarding the value and matcher type.
//
// Note: using filters with a key with prefix `$Object:` and match type
// `NOT_PRESENT `is not recommended since this is not a cross-version
// approach. Behavior when processing this kind of filters is undefined.
message Filter {
// Match type to use
MatchType match_type = 1 [ json_name = "matchType" ];
// Attribute or Header fields to match
string key = 2 [ json_name = "key" ];
// Value to match
string value = 3 [ json_name = "value" ];
}
// List of search expressions
repeated Filter filters = 3;
}
// Body of search object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Search response
message SearchResponse {
// Object Search response body
message Body {
// List of `ObjectID`s that match the search query
repeated neo.fs.v2.refs.ObjectID id_list = 1;
}
// Body of search object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Object payload range.Ranges of zero length SHOULD be considered as invalid.
message Range {
// Offset of the range from the object payload start
uint64 offset = 1;
// Length in bytes of the object payload range
uint64 length = 2;
}
// Request part of object's payload
message GetRangeRequest {
// Byte range of object's payload request body
message Body {
// Address of the object containing the requested payload range
neo.fs.v2.refs.Address address = 1;
// Requested payload range
Range range = 2;
// If `raw` flag is set, request will work only with objects that are
// physically stored on the peer node.
bool raw = 3;
}
// Body of get range object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Get part of object's payload
message GetRangeResponse {
// Get Range response body uses streams to transfer the response. Because
// object payload considered a byte sequence, there is no need to have some
// initial preamble message. The requested byte range is sent as a series
// chunks.
message Body {
// Requested object range or meta information about split object.
oneof range_part {
// Chunked object payload's range.
bytes chunk = 1;
// Meta information of split hierarchy.
SplitInfo split_info = 2;
// Meta information for EC object assembly.
ECInfo ec_info = 3;
}
}
// Body of get range object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Get hash of object's payload part
message GetRangeHashRequest {
// Get hash of object's payload part request body.
message Body {
// Address of the object that containing the requested payload range
neo.fs.v2.refs.Address address = 1;
// List of object's payload ranges to calculate homomorphic hash
repeated Range ranges = 2;
// Binary salt to XOR object's payload ranges before hash calculation
bytes salt = 3;
// Checksum algorithm type
neo.fs.v2.refs.ChecksumType type = 4;
}
// Body of get range hash object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Get hash of object's payload part
message GetRangeHashResponse {
// Get hash of object's payload part response body.
message Body {
// Checksum algorithm type
neo.fs.v2.refs.ChecksumType type = 1;
// List of range hashes in a binary format
repeated bytes hash_list = 2;
}
// Body of get range hash object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}
// Object PUT Single request
message PutSingleRequest {
// PUT Single request body
message Body {
// Prepared object with payload.
Object object = 1;
// Number of copies of the object to store within the RPC call. By default,
// object is processed according to the container's placement policy.
// Every number is treated as a minimal number of
// nodes in a corresponding placement vector that must store an object
// to complete the request successfully. The length MUST equal the placement
// vectors number, otherwise request is considered malformed.
repeated uint32 copies_number = 2;
}
// Body of put single object request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Object PUT Single response
message PutSingleResponse {
// PUT Single Object response body
message Body {}
// Body of put single object response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}

View file

@ -0,0 +1,266 @@
syntax = "proto3";
package neo.fs.v2.object;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object/grpc;object";
option java_package = "frostFS.object";
import "refs/types.proto";
import "session/types.proto";
// Type of the object payload content. Only `REGULAR` type objects can be split,
// hence `TOMBSTONE` and `LOCK` payload is limited by the
// maximum object size.
//
// String presentation of object type is the same as definition:
// * REGULAR
// * TOMBSTONE
// * LOCK
enum ObjectType {
// Just a normal object
REGULAR = 0;
// Used internally to identify deleted objects
TOMBSTONE = 1;
// Unused (previously storageGroup information)
// _ = 2;
// Object lock
LOCK = 3;
}
// Type of match expression
enum MatchType {
// Unknown. Not used
MATCH_TYPE_UNSPECIFIED = 0;
// Full string match
STRING_EQUAL = 1;
// Full string mismatch
STRING_NOT_EQUAL = 2;
// Lack of key
NOT_PRESENT = 3;
// String prefix match
COMMON_PREFIX = 4;
}
// Short header fields
message ShortHeader {
// Object format version. Effectively, the version of API library used to
// create particular object.
neo.fs.v2.refs.Version version = 1 [ json_name = "version" ];
// Epoch when the object was created
uint64 creation_epoch = 2 [ json_name = "creationEpoch" ];
// Object's owner
neo.fs.v2.refs.OwnerID owner_id = 3 [ json_name = "ownerID" ];
// Type of the object payload content
ObjectType object_type = 4 [ json_name = "objectType" ];
// Size of payload in bytes.
// `0xFFFFFFFFFFFFFFFF` means `payload_length` is unknown
uint64 payload_length = 5 [ json_name = "payloadLength" ];
// Hash of payload bytes
neo.fs.v2.refs.Checksum payload_hash = 6 [ json_name = "payloadHash" ];
// Homomorphic hash of the object payload
neo.fs.v2.refs.Checksum homomorphic_hash = 7
[ json_name = "homomorphicHash" ];
}
// Object Header
message Header {
// Object format version. Effectively, the version of API library used to
// create particular object
neo.fs.v2.refs.Version version = 1 [ json_name = "version" ];
// Object's container
neo.fs.v2.refs.ContainerID container_id = 2 [ json_name = "containerID" ];
// Object's owner
neo.fs.v2.refs.OwnerID owner_id = 3 [ json_name = "ownerID" ];
// Object creation Epoch
uint64 creation_epoch = 4 [ json_name = "creationEpoch" ];
// Size of payload in bytes.
// `0xFFFFFFFFFFFFFFFF` means `payload_length` is unknown.
uint64 payload_length = 5 [ json_name = "payloadLength" ];
// Hash of payload bytes
neo.fs.v2.refs.Checksum payload_hash = 6 [ json_name = "payloadHash" ];
// Type of the object payload content
ObjectType object_type = 7 [ json_name = "objectType" ];
// Homomorphic hash of the object payload
neo.fs.v2.refs.Checksum homomorphic_hash = 8
[ json_name = "homomorphicHash" ];
// Session token, if it was used during Object creation. Need it to verify
// 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 an
// object.
//
// 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.
//
// There are some "well-known" attributes starting with `__SYSTEM__`
// (`__NEOFS__` is deprecated) prefix that affect system behaviour:
//
// * [ __SYSTEM__UPLOAD_ID ] \
// (`__NEOFS__UPLOAD_ID` is deprecated) \
// Marks smaller parts of a split bigger object
// * [ __SYSTEM__EXPIRATION_EPOCH ] \
// (`__NEOFS__EXPIRATION_EPOCH` is deprecated) \
// The epoch after which object with no LOCKs on it becomes unavailable.
// Locked object continues to be available until each of the LOCKs expire.
// * [ __SYSTEM__TICK_EPOCH ] \
// (`__NEOFS__TICK_EPOCH` is deprecated) \
// Decimal number that defines what epoch must produce
// object notification with UTF-8 object address in a
// body (`0` value produces notification right after
// object put)
// * [ __SYSTEM__TICK_TOPIC ] \
// (`__NEOFS__TICK_TOPIC` is deprecated) \
// UTF-8 string topic ID that is used for object notification
//
// And some well-known attributes used by applications only:
//
// * Name \
// Human-friendly name
// * FileName \
// File name to be associated with the object on saving
// * FilePath \
// Full path to be associated with the object on saving. Should start with a
// '/' and use '/' as a delimiting symbol. Trailing '/' should be
// interpreted as a virtual directory marker. If an object has conflicting
// FilePath and FileName, FilePath should have higher priority, because it
// is used to construct the directory tree. FilePath with trailing '/' and
// non-empty FileName attribute should not be used together.
// * Timestamp \
// User-defined local time of object creation in Unix Timestamp format
// * Content-Type \
// MIME Content Type of object's payload
//
// For detailed description of each well-known attribute please see the
// corresponding section in NeoFS Technical Specification.
message Attribute {
// string key to the object attribute
string key = 1 [ json_name = "key" ];
// string value of the object attribute
string value = 2 [ json_name = "value" ];
}
// User-defined object attributes
repeated Attribute attributes = 10 [ json_name = "attributes" ];
// Bigger objects can be split into a chain of smaller objects. Information
// about inter-dependencies between spawned objects and how to re-construct
// the original one is in the `Split` headers. Parent and children objects
// must be within the same container.
message Split {
// Identifier of the origin object. Known only to the minor child.
neo.fs.v2.refs.ObjectID parent = 1 [ json_name = "parent" ];
// Identifier of the left split neighbor
neo.fs.v2.refs.ObjectID previous = 2 [ json_name = "previous" ];
// `signature` field of the parent object. Used to reconstruct parent.
neo.fs.v2.refs.Signature parent_signature = 3
[ json_name = "parentSignature" ];
// `header` field of the parent object. Used to reconstruct parent.
Header parent_header = 4 [ json_name = "parentHeader" ];
// List of identifiers of the objects generated by splitting current one.
repeated neo.fs.v2.refs.ObjectID children = 5 [ json_name = "children" ];
// 16 byte UUIDv4 used to identify the split object hierarchy parts. Must be
// unique inside container. All objects participating in the split must have
// the same `split_id` value.
bytes split_id = 6 [ json_name = "splitID" ];
}
// Position of the object in the split hierarchy
Split split = 11 [ json_name = "split" ];
// Erasure code can be applied to any object.
// Information about encoded object structure is stored in `EC` header.
// All objects belonging to a single EC group have the same `parent` field.
message EC {
// Identifier of the origin object. Known to all chunks.
neo.fs.v2.refs.ObjectID parent = 1 [ json_name = "parent" ];
// Index of this chunk.
uint32 index = 2 [ json_name = "index" ];
// Total number of chunks in this split.
uint32 total = 3 [ json_name = "total" ];
// Total length of a parent header. Used to trim padding zeroes.
uint32 header_length = 4 [ json_name = "headerLength" ];
// Chunk of a parent header.
bytes header = 5 [ json_name = "header" ];
}
// Erasure code chunk information.
EC ec = 12 [ json_name = "ec" ];
}
// Object structure. Object is immutable and content-addressed. It means
// `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.
message Object {
// Object's unique identifier.
neo.fs.v2.refs.ObjectID object_id = 1 [ json_name = "objectID" ];
// Signed object_id
neo.fs.v2.refs.Signature signature = 2 [ json_name = "signature" ];
// Object metadata headers
Header header = 3 [ json_name = "header" ];
// Payload bytes
bytes payload = 4 [ json_name = "payload" ];
}
// 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;
// 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;
// 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;
}
// Meta information for the erasure-encoded object.
message ECInfo {
message Chunk {
// Object ID of the chunk.
neo.fs.v2.refs.ObjectID id = 1;
// Index of the chunk.
uint32 index = 2;
// Total number of chunks in this split.
uint32 total = 3;
}
// Chunk stored on the node.
repeated Chunk chunks = 1;
}

View file

@ -0,0 +1,150 @@
syntax = "proto3";
package neo.fs.v2.refs;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs/grpc;refs";
option java_package = "frostFS.refs";
// Objects in NeoFS are addressed by their ContainerID and ObjectID.
//
// String presentation of `Address` is a concatenation of string encoded
// `ContainerID` and `ObjectID` delimited by '/' character.
message Address {
// Container identifier
ContainerID container_id = 1 [ json_name = "containerID" ];
// Object identifier
ObjectID object_id = 2 [ json_name = "objectID" ];
}
// NeoFS Object unique identifier. Objects are immutable and content-addressed.
// 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
// the object's `header` field, which, in it's turn, contains the hash of the
// object's payload.
//
// String presentation is a
// [base58](https://tools.ietf.org/html/draft-msporny-base58-02) encoded string.
//
// 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
// with/without paddings are accepted.
message ObjectID {
// Object identifier in a binary format
bytes value = 1 [ json_name = "value" ];
}
// NeoFS container identifier. Container structures are immutable and
// content-addressed.
//
// `ContainerID` is a 32 byte long
// [SHA256](https://csrc.nist.gov/publications/detail/fips/180/4/final) hash of
// stable-marshalled container message.
//
// String presentation is a
// [base58](https://tools.ietf.org/html/draft-msporny-base58-02) encoded string.
//
// 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
// with/without paddings are accepted.
message ContainerID {
// Container identifier in a binary format.
bytes value = 1 [ json_name = "value" ];
}
// `OwnerID` is a derivative of a user's main public key. The transformation
// algorithm is the same as for Neo3 wallet addresses. Neo3 wallet address can
// be directly used as `OwnerID`.
//
// `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 a [Base58
// Check](https://en.bitcoin.it/wiki/Base58Check_encoding) Encoded string.
//
// 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
// with/without paddings are accepted.
message OwnerID {
// Identifier of the container owner in a binary format
bytes value = 1 [ json_name = "value" ];
}
// 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` is the major number, `Y` is the minor
// number.
message Version {
// Major API version
uint32 major = 1 [ json_name = "major" ];
// Minor API version
uint32 minor = 2 [ json_name = "minor" ];
}
// Signature of something in NeoFS.
message Signature {
// Public key used for signing
bytes key = 1 [ json_name = "key" ];
// Signature
bytes sign = 2 [ json_name = "signature" ];
// Scheme contains digital signature scheme identifier
SignatureScheme scheme = 3 [ json_name = "scheme" ];
}
// Signature scheme describes digital signing scheme used for (key, signature)
// pair.
enum SignatureScheme {
// ECDSA with SHA-512 hashing (FIPS 186-3)
ECDSA_SHA512 = 0;
// Deterministic ECDSA with SHA-256 hashing (RFC 6979)
ECDSA_RFC6979_SHA256 = 1;
// Deterministic ECDSA with SHA-256 hashing using WalletConnect API.
// Here the algorithm is the same, but the message format differs.
ECDSA_RFC6979_SHA256_WALLET_CONNECT = 2;
}
// RFC 6979 signature.
message SignatureRFC6979 {
// Public key used for signing
bytes key = 1 [ json_name = "key" ];
// Deterministic ECDSA with SHA-256 hashing
bytes sign = 2 [ json_name = "signature" ];
}
// Checksum algorithm type.
enum ChecksumType {
// Unknown. Not used
CHECKSUM_TYPE_UNSPECIFIED = 0;
// Tillich-Zemor homomorphic hash function
TZ = 1;
// SHA-256
SHA256 = 2;
}
// Checksum message.
// Depending on checksum algorithm type, the string presentation may vary:
//
// * TZ \
// Hex encoded string without `0x` prefix
// * SHA256 \
// Hex encoded string without `0x` prefix
message Checksum {
// Checksum algorithm type
ChecksumType type = 1 [ json_name = "type" ];
// Checksum itself
bytes sum = 2 [ json_name = "sum" ];
}

View file

@ -0,0 +1,69 @@
syntax = "proto3";
package neo.fs.v2.session;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session/grpc;session";
option java_package = "frostFS.session";
import "refs/types.proto";
import "session/types.proto";
// `SessionService` allows to establish a temporary trust relationship between
// two peer nodes and generate a `SessionToken` as the proof of trust to be
// attached in requests for further verification. Please see corresponding
// section of NeoFS Technical Specification for details.
service SessionService {
// Open a new session between two peers.
//
// Statuses:
// - **OK** (0, SECTION_SUCCESS):
// session has been successfully opened;
// - Common failures (SECTION_FAILURE_COMMON).
rpc Create(CreateRequest) returns (CreateResponse);
}
// Information necessary for opening a session.
message CreateRequest {
// Session creation request body
message Body {
// Session initiating user's or node's key derived `OwnerID`
neo.fs.v2.refs.OwnerID owner_id = 1;
// Session expiration `Epoch`
uint64 expiration = 2;
}
// Body of a create session token request message.
Body body = 1;
// Carries request meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.RequestMetaHeader meta_header = 2;
// Carries request verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.RequestVerificationHeader verify_header = 3;
}
// Information about the opened session.
message CreateResponse {
// Session creation response body
message Body {
// Identifier of a newly created session
bytes id = 1;
// Public key used for session
bytes session_key = 2;
}
// Body of create session token response message.
Body body = 1;
// Carries response meta information. Header data is used only to regulate
// message transport and does not affect request execution.
neo.fs.v2.session.ResponseMetaHeader meta_header = 2;
// Carries response verification information. This header is used to
// authenticate the nodes of the message route and check the correctness of
// transmission.
neo.fs.v2.session.ResponseVerificationHeader verify_header = 3;
}

View file

@ -0,0 +1,238 @@
syntax = "proto3";
package neo.fs.v2.session;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session/grpc;session";
option java_package = "frostFS.session";
import "refs/types.proto";
import "acl/types.proto";
import "status/types.proto";
// Context information for Session Tokens related to ObjectService requests
message ObjectSessionContext {
// Object request verbs
enum Verb {
// Unknown verb
VERB_UNSPECIFIED = 0;
// Refers to object.Put RPC call
PUT = 1;
// Refers to object.Get RPC call
GET = 2;
// Refers to object.Head RPC call
HEAD = 3;
// Refers to object.Search RPC call
SEARCH = 4;
// Refers to object.Delete RPC call
DELETE = 5;
// Refers to object.GetRange RPC call
RANGE = 6;
// Refers to object.GetRangeHash RPC call
RANGEHASH = 7;
}
// Type of request for which the token is issued
Verb verb = 1 [ json_name = "verb" ];
// Carries objects involved in the object session.
message Target {
// Indicates which container the session is spread to. Field MUST be set
// and correct.
refs.ContainerID container = 1 [ json_name = "container" ];
// Indicates which objects the session is spread to. Objects are expected
// to be stored in the NeoFS container referenced by `container` field.
// Each element MUST have correct format.
repeated refs.ObjectID objects = 2 [ json_name = "objects" ];
}
// Object session target. MUST be correctly formed and set. If `objects`
// field is not empty, then the session applies only to these elements,
// otherwise, to all objects from the specified container.
Target target = 2 [ json_name = "target" ];
}
// Context information for Session Tokens related to ContainerService requests.
message ContainerSessionContext {
// Container request verbs
enum Verb {
// Unknown verb
VERB_UNSPECIFIED = 0;
// Refers to container.Put RPC call
PUT = 1;
// Refers to container.Delete RPC call
DELETE = 2;
// Refers to container.SetExtendedACL RPC call
SETEACL = 3;
}
// Type of request for which the token is issued
Verb verb = 1 [ json_name = "verb" ];
// Spreads the action to all owner containers.
// If set, container_id field is ignored.
bool wildcard = 2 [ json_name = "wildcard" ];
// Particular container to which the action applies.
// Ignored if wildcard flag is set.
refs.ContainerID container_id = 3 [ json_name = "containerID" ];
}
// NeoFS Session Token.
message SessionToken {
// Session Token body
message Body {
// Token identifier is a valid UUIDv4 in binary form
bytes id = 1 [ json_name = "id" ];
// Identifier of the session initiator
neo.fs.v2.refs.OwnerID owner_id = 2 [ json_name = "ownerID" ];
// Lifetime parameters of the token. Field names taken from rfc7519.
message TokenLifetime {
// Expiration Epoch
uint64 exp = 1 [ json_name = "exp" ];
// Not valid before Epoch
uint64 nbf = 2 [ json_name = "nbf" ];
// Issued at Epoch
uint64 iat = 3 [ json_name = "iat" ];
}
// Lifetime of the session
TokenLifetime lifetime = 3 [ json_name = "lifetime" ];
// Public key used in session
bytes session_key = 4 [ json_name = "sessionKey" ];
// Session Context information
oneof context {
// ObjectService session context
ObjectSessionContext object = 5 [ json_name = "object" ];
// ContainerService session context
ContainerSessionContext container = 6 [ json_name = "container" ];
}
}
// Session Token contains the proof of trust between peers to be attached in
// requests for further verification. Please see corresponding section of
// NeoFS Technical Specification for details.
Body body = 1 [ json_name = "body" ];
// Signature of `SessionToken` information
neo.fs.v2.refs.Signature signature = 2 [ json_name = "signature" ];
}
// Extended headers for Request/Response. They may contain any user-defined
// headers to be interpreted on application level.
//
// 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.
//
// There are some "well-known" headers starting with `__SYSTEM__` (`__NEOFS__`
// is deprecated) prefix that affect system behaviour:
//
// * [ __SYSTEM__NETMAP_EPOCH ] \
// (`__NEOFS__NETMAP_EPOCH` is deprecated) \
// Netmap epoch to use for object placement calculation. The `value` is string
// encoded `uint64` in decimal presentation. If set to '0' or not set, the
// current epoch only will be used.
// * [ __SYSTEM__NETMAP_LOOKUP_DEPTH ] \
// (`__NEOFS__NETMAP_LOOKUP_DEPTH` is deprecated) \
// If object can't be found using current epoch's netmap, this header limits
// 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" ];
// Value of the X-Header
string value = 2 [ json_name = "value" ];
}
// Meta information attached to the request. When forwarded between peers,
// request meta headers are folded in matryoshka style.
message RequestMetaHeader {
// Peer's API version used
neo.fs.v2.refs.Version version = 1 [ json_name = "version" ];
// Peer's local epoch number. Set to 0 if unknown.
uint64 epoch = 2 [ json_name = "epoch" ];
// Maximum number of intermediate nodes in the request route
uint32 ttl = 3 [ json_name = "ttl" ];
// Request X-Headers
repeated XHeader x_headers = 4 [ json_name = "xHeaders" ];
// Session token within which the request is sent
SessionToken session_token = 5 [ json_name = "sessionToken" ];
// `BearerToken` with eACL overrides for the request
neo.fs.v2.acl.BearerToken bearer_token = 6 [ json_name = "bearerToken" ];
// `RequestMetaHeader` of the origin request
RequestMetaHeader origin = 7 [ json_name = "origin" ];
// NeoFS network magic. Must match the value for the network
// that the server belongs to.
uint64 magic_number = 8 [ json_name = "magicNumber" ];
}
// Information about the response
message ResponseMetaHeader {
// Peer's API version used
neo.fs.v2.refs.Version version = 1 [ json_name = "version" ];
// Peer's local epoch number
uint64 epoch = 2 [ json_name = "epoch" ];
// Maximum number of intermediate nodes in the request route
uint32 ttl = 3 [ json_name = "ttl" ];
// Response X-Headers
repeated XHeader x_headers = 4 [ json_name = "xHeaders" ];
// `ResponseMetaHeader` of the origin request
ResponseMetaHeader origin = 5 [ json_name = "origin" ];
// Status return
neo.fs.v2.status.Status status = 6 [ json_name = "status" ];
}
// Verification info for the request signed by all intermediate nodes.
message RequestVerificationHeader {
// 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" ];
// Signature of previous hops
neo.fs.v2.refs.Signature origin_signature = 3
[ json_name = "originSignature" ];
// Chain of previous hops signatures
RequestVerificationHeader origin = 4 [ json_name = "origin" ];
}
// Verification info for the response signed by all intermediate nodes
message ResponseVerificationHeader {
// 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" ];
// Signature of previous hops
neo.fs.v2.refs.Signature origin_signature = 3
[ json_name = "originSignature" ];
// Chain of previous hops signatures
ResponseVerificationHeader origin = 4 [ json_name = "origin" ];
}

View file

@ -0,0 +1,157 @@
syntax = "proto3";
package neo.fs.v2.status;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status/grpc;status";
option java_package = "frostFS.status";
// Declares the general format of the status returns of the NeoFS RPC protocol.
// Status is present in all response messages. Each RPC of NeoFS protocol
// describes the possible outcomes and details of the operation.
//
// Each status is assigned a one-to-one numeric code. Any unique result of an
// operation in NeoFS is unambiguously associated with the code value.
//
// Numerical set of codes is split into 1024-element sections. An enumeration
// is defined for each section. Values can be referred to in the following ways:
//
// * numerical value ranging from 0 to 4,294,967,295 (global code);
//
// * values from enumeration (local code). The formula for the ratio of the
// local code (`L`) of a defined section (`S`) to the global one (`G`):
// `G = 1024 * S + L`.
//
// All outcomes are divided into successful and failed, which corresponds
// to the success or failure of the operation. The definition of success
// 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 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.
message Status {
// The status code
uint32 code = 1;
// Developer-facing error message
string message = 2;
// Return detail. It contains additional information that can be used to
// analyze the response. Each code defines a set of details that can be
// attached to a status. Client should not handle details that are not
// covered by the code.
message Detail {
// Detail ID. The identifier is required to determine the binary format
// of the detail and how to decode it.
uint32 id = 1;
// Binary status detail. Must follow the format associated with ID.
// The possibility of missing a value must be explicitly allowed.
bytes value = 2;
}
// Data detailing the outcome of the operation. Must be unique by ID.
repeated Detail details = 3;
}
// Section identifiers.
enum Section {
// Successful return codes.
SECTION_SUCCESS = 0;
// Failure codes regardless of the operation.
SECTION_FAILURE_COMMON = 1;
// Object service-specific errors.
SECTION_OBJECT = 2;
// Container service-specific errors.
SECTION_CONTAINER = 3;
// Session service-specific errors.
SECTION_SESSION = 4;
// Session service-specific errors.
SECTION_APE_MANAGER = 5;
}
// Section of NeoFS successful return codes.
enum Success {
// [**0**] Default success. Not detailed.
// If the server cannot match successful outcome to the code, it should
// use this code.
OK = 0;
}
// Section of failed statuses independent of the operation.
enum CommonFail {
// [**1024**] Internal server error, default failure. Not detailed.
// If the server cannot match failed outcome to the code, it should
// use this code.
INTERNAL = 0;
// [**1025**] Wrong magic of the NeoFS network.
// Details:
// - [**0**] Magic number of the served NeoFS network (big-endian 64-bit
// unsigned integer).
WRONG_MAGIC_NUMBER = 1;
// [**1026**] Signature verification failure.
SIGNATURE_VERIFICATION_FAIL = 2;
// [**1027**] Node is under maintenance.
NODE_UNDER_MAINTENANCE = 3;
}
// Section of statuses for object-related operations.
enum Object {
// [**2048**] Access denied by ACL.
// Details:
// - [**0**] Human-readable description (UTF-8 encoded string).
ACCESS_DENIED = 0;
// [**2049**] Object not found.
OBJECT_NOT_FOUND = 1;
// [**2050**] Operation rejected by the object lock.
LOCKED = 2;
// [**2051**] Locking an object with a non-REGULAR type rejected.
LOCK_NON_REGULAR_OBJECT = 3;
// [**2052**] Object has been marked deleted.
OBJECT_ALREADY_REMOVED = 4;
// [**2053**] Invalid range has been requested for an object.
OUT_OF_RANGE = 5;
}
// Section of statuses for container-related operations.
enum Container {
// [**3072**] Container not found.
CONTAINER_NOT_FOUND = 0;
// [**3073**] eACL table not found.
EACL_NOT_FOUND = 1;
// [**3074**] Container access denied.
CONTAINER_ACCESS_DENIED = 2;
}
// Section of statuses for session-related operations.
enum Session {
// [**4096**] Token not found.
TOKEN_NOT_FOUND = 0;
// [**4097**] Token has expired.
TOKEN_EXPIRED = 1;
}
// Section of status for APE manager related operations.
enum APEManager {
// [**5120**] The operation is denied by APE manager.
APE_MANAGER_ACCESS_DENIED = 0;
}

View file

@ -0,0 +1,27 @@
syntax = "proto3";
package neo.fs.v2.tombstone;
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/tombstone/grpc;tombstone";
option java_package = "frostFS.tombstone";
import "refs/types.proto";
// 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 the
// tombstone creator depending on the current NeoFS network settings. A
// tombstone object must have the same expiration epoch value in
// `__SYSTEM__EXPIRATION_EPOCH` (`__NEOFS__EXPIRATION_EPOCH` is deprecated)
// 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 a container. All objects participating in the split must
// have the same `split_id` value.
bytes split_id = 2 [ json_name = "splitID" ];
// List of objects to be deleted.
repeated neo.fs.v2.refs.ObjectID members = 3 [ json_name = "members" ];
}