[#1] Define SDK main functional #1

Merged
orikik merged 7 commits from orikik/frostfs-sdk-java:master into master 2024-09-04 19:51:24 +00:00
103 changed files with 7886 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,97 @@
# 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 operations
orikik marked this conversation as resolved Outdated
Outdated
Review

Container operations maybe?

Container operations maybe?

agree, corrected

agree, corrected
```java
import info.frostfs.sdk.dto.container.Container;
import info.frostfs.sdk.dto.netmap.PlacementPolicy;
import info.frostfs.sdk.dto.netmap.Replica;
import info.frostfs.sdk.enums.BasicAcl;
import info.frostfs.sdk.jdo.ClientSettings;
import info.frostfs.sdk.FrostFSClient;
public class ContainerExample {
public void example() {
ClientSettings clientSettings = new ClientSettings(<your_key>, <your_host>);
FrostFSClient frostFSClient = new FrostFSClient(clientSettings);
// Create container
var placementPolicy = new PlacementPolicy(true, new Replica[]{new Replica(1)});
var containerId = frostFSClient.createContainer(new Container(BasicAcl.PUBLIC_RW, placementPolicy));
// Get container
var container = frostFSClient.getContainer(containerId);
// List containers
var containerIds = frostFSClient.listContainers();
// Delete container
frostFSClient.deleteContainer(containerId);
}
}
```
### Object operations
orikik marked this conversation as resolved Outdated
Outdated
Review

Object operations maybe?

Object operations maybe?

agree, corrected

agree, corrected
```java
import info.frostfs.sdk.enums.ObjectType;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.dto.object.ObjectAttribute;
import info.frostfs.sdk.dto.object.ObjectFilter;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.dto.object.ObjectId;
import info.frostfs.sdk.jdo.PutObjectParameters;
import info.frostfs.sdk.FrostFSClient;
import java.io.FileInputStream;
import java.io.IOException;
public class ObjectExample {
public void example() {
ClientSettings clientSettings = new ClientSettings(<your_key>, <your_host>);
FrostFSClient frostFSClient = new FrostFSClient(clientSettings);
// Put object
ObjectId objectId;
try (FileInputStream fis = new FileInputStream("cat.jpg")) {
var cat = new ObjectHeader(
containerId, ObjectType.REGULAR, new ObjectAttribute[]{new ObjectAttribute("Filename", "cat.jpg")}
);
var params = new PutObjectParameters(cat, fis);
objectId = frostFSClient.putObject(params);
} catch (IOException e) {
throw new RuntimeException(e);
}
// Get object
var obj = frostFSClient.getObject(containerId, objectId);
// Get object header
var objectHeader = frostFSClient.getObjectHead(containerId, objectId);
// Search regular objects
var objectIds = frostFSClient.searchObjects(containerId, ObjectFilter.RootFilter());
}
}
```

43
client/pom.xml Normal file
View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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>models</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.17.0</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,139 @@
package info.frostfs.sdk;
import frostfs.session.Types;
import info.frostfs.sdk.dto.SessionToken;
import info.frostfs.sdk.dto.Version;
import info.frostfs.sdk.dto.container.Container;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.dto.netmap.NetmapSnapshot;
import info.frostfs.sdk.dto.netmap.NodeInfo;
import info.frostfs.sdk.dto.object.ObjectFilter;
import info.frostfs.sdk.dto.object.ObjectFrostFS;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.dto.object.ObjectId;
import info.frostfs.sdk.jdo.ClientEnvironment;
import info.frostfs.sdk.jdo.ClientSettings;
import info.frostfs.sdk.jdo.NetworkSettings;
import info.frostfs.sdk.jdo.PutObjectParameters;
import info.frostfs.sdk.services.*;
import info.frostfs.sdk.services.impl.*;
import io.grpc.Channel;
import java.util.List;
import static info.frostfs.sdk.tools.GrpcClient.initGrpcChannel;
import static java.util.Objects.isNull;
public class FrostFSClient implements ContainerClient, ObjectClient, NetmapClient, SessionClient, ToolsClient {
private static final String ERROR_CLIENT_OPTIONS_INIT = "Options must be initialized.";
private static final String ERROR_VERSION_SUPPORT_TEMPLATE = "FrostFS %s is not supported.";
private final ContainerClientImpl containerClientImpl;
private final NetmapClientImpl netmapClientImpl;
private final ObjectClientImpl objectClientImpl;
private final SessionClientImpl sessionClientImpl;
private final ObjectToolsImpl objectToolsImpl;
public FrostFSClient(ClientSettings clientSettings) {
if (isNull(clientSettings)) {
throw new IllegalArgumentException(ERROR_CLIENT_OPTIONS_INIT);
}
clientSettings.validate();
Channel channel = initGrpcChannel(clientSettings.getHost(), clientSettings.getCreds());
ClientEnvironment clientEnvironment =
new ClientEnvironment(clientSettings.getKey(), channel, new Version(), this);
this.containerClientImpl = new ContainerClientImpl(clientEnvironment);
this.netmapClientImpl = new NetmapClientImpl(clientEnvironment);
this.sessionClientImpl = new SessionClientImpl(clientEnvironment);
this.objectClientImpl = new ObjectClientImpl(clientEnvironment);
this.objectToolsImpl = new ObjectToolsImpl(clientEnvironment);
checkFrostFsVersionSupport(clientEnvironment.getVersion());
}
private void checkFrostFsVersionSupport(Version version) {
var localNodeInfo = netmapClientImpl.getLocalNodeInfo();
if (!localNodeInfo.getVersion().isSupported(version)) {
throw new IllegalArgumentException(
String.format(ERROR_VERSION_SUPPORT_TEMPLATE, localNodeInfo.getVersion())
);
}
}
@Override
public Container getContainer(ContainerId cid) {
return containerClientImpl.getContainer(cid);
}
@Override
public List<ContainerId> listContainers() {
return containerClientImpl.listContainers();
}
@Override
public ContainerId createContainer(Container container) {
return containerClientImpl.createContainer(container);
}
@Override
public void deleteContainer(ContainerId cid) {
containerClientImpl.deleteContainer(cid);
}
@Override
public ObjectHeader getObjectHead(ContainerId containerId, ObjectId objectId) {
return objectClientImpl.getObjectHead(containerId, objectId);
}
@Override
public ObjectFrostFS getObject(ContainerId containerId, ObjectId objectId) {
return objectClientImpl.getObject(containerId, objectId);
}
@Override
public ObjectId putObject(PutObjectParameters parameters) {
return objectClientImpl.putObject(parameters);
}
@Override
public void deleteObject(ContainerId containerId, ObjectId objectId) {
objectClientImpl.deleteObject(containerId, objectId);
}
@Override
public Iterable<ObjectId> searchObjects(ContainerId cid, ObjectFilter... filters) {
return objectClientImpl.searchObjects(cid, filters);
}
@Override
public NetmapSnapshot getNetmapSnapshot() {
return netmapClientImpl.getNetmapSnapshot();
}
@Override
public NodeInfo getLocalNodeInfo() {
return netmapClientImpl.getLocalNodeInfo();
}
@Override
public NetworkSettings getNetworkSettings() {
return netmapClientImpl.getNetworkSettings();
}
@Override
public SessionToken createSession(long expiration) {
return sessionClientImpl.createSession(expiration);
}
public Types.SessionToken createSessionInternal(long expiration) {
return sessionClientImpl.createSessionInternal(expiration);
}
@Override
public ObjectId calculateObjectId(ObjectHeader header) {
return objectToolsImpl.calculateObjectId(header);
}
}

View file

@ -0,0 +1,8 @@
package info.frostfs.sdk.constants;
public class CryptoConst {
public static final String SIGNATURE_ALGORITHM = "NONEwithECDSAinP1363Format";
private CryptoConst() {
}
}

View file

@ -0,0 +1,58 @@
package info.frostfs.sdk.jdo;
import info.frostfs.sdk.dto.OwnerId;
import info.frostfs.sdk.dto.Version;
import info.frostfs.sdk.FrostFSClient;
import io.grpc.Channel;
import org.apache.commons.lang3.StringUtils;
import static java.util.Objects.isNull;
public class ClientEnvironment {
private final OwnerId ownerId;
private final Version version;
private final ECDsa key;
private final Channel channel;
private final FrostFSClient frostFSClient;
private NetworkSettings networkSettings;
public ClientEnvironment(String wif, Channel channel, Version version, FrostFSClient frostFSClient) {
if (StringUtils.isEmpty(wif) || isNull(channel) || isNull(version) || isNull(frostFSClient)) {
throw new IllegalArgumentException("One of the input attributes is missing");
}
this.key = new ECDsa(wif);
this.ownerId = new OwnerId(key.getPublicKeyByte());
this.version = version;
this.channel = channel;
this.frostFSClient = frostFSClient;
}
public Channel getChannel() {
return channel;
}
public NetworkSettings getNetworkSettings() {
return networkSettings;
}
public void setNetworkSettings(NetworkSettings networkSettings) {
this.networkSettings = networkSettings;
}
public FrostFSClient getFrostFSClient() {
return frostFSClient;
}
public OwnerId getOwnerId() {
return ownerId;
}
public Version getVersion() {
return version;
}
public ECDsa getKey() {
return key;
}
}

View file

@ -0,0 +1,65 @@
package info.frostfs.sdk.jdo;
import io.grpc.ChannelCredentials;
import org.apache.commons.lang3.StringUtils;
public class ClientSettings {
private static final String ERROR_TEMPLATE = "%s is required parameter.";
public String key;
public String host;
public ChannelCredentials creds;
public ClientSettings(String key, String host) {
this.key = key;
this.host = host;
validate();
}
public ClientSettings(String key, String host, ChannelCredentials creds) {
this.key = key;
this.host = host;
this.creds = creds;
validate();
}
public ChannelCredentials getCreds() {
return creds;
}
public void setCreds(ChannelCredentials creds) {
this.creds = creds;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public void validate() {
StringBuilder errorMessage = new StringBuilder();
if (StringUtils.isEmpty(key)) {
errorMessage.append(String.format(ERROR_TEMPLATE, "Key")).append(System.lineSeparator());
}
if (StringUtils.isEmpty(host)) {
errorMessage.append(String.format(ERROR_TEMPLATE, "Host")).append(System.lineSeparator());
}
if (errorMessage.length() != 0) {
throw new IllegalArgumentException(errorMessage.toString());
}
}
}

View file

@ -0,0 +1,35 @@
package info.frostfs.sdk.jdo;
import org.apache.commons.lang3.StringUtils;
import java.security.PrivateKey;
import static info.frostfs.sdk.KeyExtension.*;
public class ECDsa {
private final byte[] publicKeyByte;
private final byte[] privateKeyByte;
private final PrivateKey privateKey;
public ECDsa(String wif) {
if (StringUtils.isEmpty(wif)) {
throw new IllegalArgumentException("Wif is invalid");
}
this.privateKeyByte = getPrivateKeyFromWIF(wif);
this.publicKeyByte = loadPublicKey(privateKeyByte);
this.privateKey = loadPrivateKey(privateKeyByte);
}
public byte[] getPublicKeyByte() {
return publicKeyByte;
}
public byte[] getPrivateKeyByte() {
return privateKeyByte;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
}

View file

@ -0,0 +1,143 @@
package info.frostfs.sdk.jdo;
import java.util.HashMap;
import java.util.Map;
public class NetworkSettings {
public Long auditFee;
public Long basicIncomeRate;
public Long containerFee;
public Long containerAliasFee;
public Long innerRingCandidateFee;
public Long withdrawFee;
public Long epochDuration;
public Long iRCandidateFee;
public Long maxObjectSize;
public Long maxECDataCount;
public Long maxECParityCount;
public Long withdrawalFee;
public Boolean homomorphicHashingDisabled;
public Boolean maintenanceModeAllowed;
public Map<String, Object> unnamedSettings = new HashMap<>();
public Long getAuditFee() {
return auditFee;
}
public void setAuditFee(Long auditFee) {
this.auditFee = auditFee;
}
public Long getBasicIncomeRate() {
return basicIncomeRate;
}
public void setBasicIncomeRate(Long basicIncomeRate) {
this.basicIncomeRate = basicIncomeRate;
}
public long getContainerFee() {
return containerFee;
}
public void setContainerFee(long containerFee) {
this.containerFee = containerFee;
}
public long getContainerAliasFee() {
return containerAliasFee;
}
public void setContainerAliasFee(long containerAliasFee) {
this.containerAliasFee = containerAliasFee;
}
public long getInnerRingCandidateFee() {
return innerRingCandidateFee;
}
public void setInnerRingCandidateFee(long innerRingCandidateFee) {
this.innerRingCandidateFee = innerRingCandidateFee;
}
public long getWithdrawFee() {
return withdrawFee;
}
public void setWithdrawFee(long withdrawFee) {
this.withdrawFee = withdrawFee;
}
public long getEpochDuration() {
return epochDuration;
}
public void setEpochDuration(long epochDuration) {
this.epochDuration = epochDuration;
}
public long getiRCandidateFee() {
return iRCandidateFee;
}
public void setiRCandidateFee(long iRCandidateFee) {
this.iRCandidateFee = iRCandidateFee;
}
public long getMaxObjectSize() {
return maxObjectSize;
}
public void setMaxObjectSize(long maxObjectSize) {
this.maxObjectSize = maxObjectSize;
}
public long getMaxECDataCount() {
return maxECDataCount;
}
public void setMaxECDataCount(long maxECDataCount) {
this.maxECDataCount = maxECDataCount;
}
public long getMaxECParityCount() {
return maxECParityCount;
}
public void setMaxECParityCount(long maxECParityCount) {
this.maxECParityCount = maxECParityCount;
}
public long getWithdrawalFee() {
return withdrawalFee;
}
public void setWithdrawalFee(long withdrawalFee) {
this.withdrawalFee = withdrawalFee;
}
public boolean isHomomorphicHashingDisabled() {
return homomorphicHashingDisabled;
}
public void setHomomorphicHashingDisabled(boolean homomorphicHashingDisabled) {
this.homomorphicHashingDisabled = homomorphicHashingDisabled;
}
public boolean isMaintenanceModeAllowed() {
return maintenanceModeAllowed;
}
public void setMaintenanceModeAllowed(boolean maintenanceModeAllowed) {
this.maintenanceModeAllowed = maintenanceModeAllowed;
}
public Map<String, Object> getUnnamedSettings() {
return unnamedSettings;
}
public void setUnnamedSettings(Map<String, Object> unnamedSettings) {
this.unnamedSettings = unnamedSettings;
}
}

View file

@ -0,0 +1,80 @@
package info.frostfs.sdk.jdo;
import info.frostfs.sdk.dto.object.ObjectHeader;
import java.io.FileInputStream;
import static java.util.Objects.isNull;
public class PutObjectParameters {
private static final String ERROR_TEMPLATE = "%s value cannot be null.";
public ObjectHeader header;
public FileInputStream payload;
public boolean clientCut;
public int bufferMaxSize;
public PutObjectParameters(ObjectHeader header, FileInputStream payload, boolean clientCut, int bufferMaxSize) {
this.header = header;
this.payload = payload;
this.clientCut = clientCut;
this.bufferMaxSize = bufferMaxSize;
validate();
}
public PutObjectParameters(ObjectHeader header, FileInputStream payload) {
this.header = header;
this.payload = payload;
validate();
}
public ObjectHeader getHeader() {
return header;
}
public void setHeader(ObjectHeader header) {
this.header = header;
}
public FileInputStream getPayload() {
return payload;
}
public void setPayload(FileInputStream payload) {
this.payload = payload;
}
public boolean isClientCut() {
return clientCut;
}
public void setClientCut(boolean clientCut) {
this.clientCut = clientCut;
}
public int getBufferMaxSize() {
return bufferMaxSize;
}
public void setBufferMaxSize(int bufferMaxSize) {
this.bufferMaxSize = bufferMaxSize;
}
public void validate() {
StringBuilder errorMessage = new StringBuilder();
if (isNull(header)) {
errorMessage.append(String.format(ERROR_TEMPLATE, "Header")).append(System.lineSeparator());
}
if (isNull(payload)) {
errorMessage.append(String.format(ERROR_TEMPLATE, "Payload")).append(System.lineSeparator());
}
if (errorMessage.length() != 0) {
throw new IllegalArgumentException(errorMessage.toString());
}
}
}

View file

@ -0,0 +1,16 @@
package info.frostfs.sdk.services;
import info.frostfs.sdk.dto.container.Container;
import info.frostfs.sdk.dto.container.ContainerId;
import java.util.List;
public interface ContainerClient {
Container getContainer(ContainerId cid);
List<ContainerId> listContainers();
ContainerId createContainer(Container container);
void deleteContainer(ContainerId cid);
}

View file

@ -0,0 +1,15 @@
package info.frostfs.sdk.services;
import info.frostfs.sdk.jdo.ClientEnvironment;
public class ContextAccessor {
private final ClientEnvironment context;
public ContextAccessor(ClientEnvironment context) {
this.context = context;
}
public ClientEnvironment getContext() {
return context;
}
}

View file

@ -0,0 +1,13 @@
package info.frostfs.sdk.services;
import info.frostfs.sdk.dto.netmap.NetmapSnapshot;
import info.frostfs.sdk.dto.netmap.NodeInfo;
import info.frostfs.sdk.jdo.NetworkSettings;
public interface NetmapClient {
NetmapSnapshot getNetmapSnapshot();
NodeInfo getLocalNodeInfo();
NetworkSettings getNetworkSettings();
}

View file

@ -0,0 +1,20 @@
package info.frostfs.sdk.services;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.dto.object.ObjectFilter;
import info.frostfs.sdk.dto.object.ObjectFrostFS;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.dto.object.ObjectId;
import info.frostfs.sdk.jdo.PutObjectParameters;
public interface ObjectClient {
ObjectHeader getObjectHead(ContainerId containerId, ObjectId objectId);
ObjectFrostFS getObject(ContainerId containerId, ObjectId objectId);
ObjectId putObject(PutObjectParameters parameters);
void deleteObject(ContainerId containerId, ObjectId objectId);
Iterable<ObjectId> searchObjects(ContainerId cid, ObjectFilter... filters);
}

View file

@ -0,0 +1,7 @@
package info.frostfs.sdk.services;
import info.frostfs.sdk.dto.SessionToken;
public interface SessionClient {
SessionToken createSession(long expiration);
}

View file

@ -0,0 +1,8 @@
package info.frostfs.sdk.services;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.dto.object.ObjectId;
public interface ToolsClient {
ObjectId calculateObjectId(ObjectHeader header);
}

View file

@ -0,0 +1,122 @@
package info.frostfs.sdk.services.impl;
import frostfs.container.ContainerServiceGrpc;
import frostfs.container.Service;
import info.frostfs.sdk.dto.container.Container;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.jdo.ClientEnvironment;
import info.frostfs.sdk.mappers.OwnerIdMapper;
import info.frostfs.sdk.mappers.VersionMapper;
import info.frostfs.sdk.mappers.container.ContainerIdMapper;
import info.frostfs.sdk.mappers.container.ContainerMapper;
import info.frostfs.sdk.services.ContainerClient;
import info.frostfs.sdk.services.ContextAccessor;
import info.frostfs.sdk.tools.RequestConstructor;
import info.frostfs.sdk.tools.RequestSigner;
import info.frostfs.sdk.tools.Verifier;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
public class ContainerClientImpl extends ContextAccessor implements ContainerClient {
private final ContainerServiceGrpc.ContainerServiceBlockingStub serviceBlockingStub;
public ContainerClientImpl(ClientEnvironment clientEnvironment) {
super(clientEnvironment);
this.serviceBlockingStub = ContainerServiceGrpc.newBlockingStub(clientEnvironment.getChannel());
}
@Override
public Container getContainer(ContainerId cid) {
if (isNull(cid)) {
throw new IllegalArgumentException("ContainerId is not present");
}
var body = Service.GetRequest.Body.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.build();
var request = Service.GetRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
RequestSigner.sign(request, getContext().getKey());
var response = serviceBlockingStub.get(request.build());
Verifier.checkResponse(response);
return ContainerMapper.toModel(response.getBody().getContainer());
}
@Override
public List<ContainerId> listContainers() {
var body = Service.ListRequest.Body.newBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(getContext().getOwnerId()))
.build();
var request = Service.ListRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
RequestSigner.sign(request, getContext().getKey());
var response = serviceBlockingStub.list(request.build());
Verifier.checkResponse(response);
return response.getBody().getContainerIdsList().stream()
.map(cid -> new ContainerId(cid.getValue().toByteArray()))
.collect(Collectors.toList());
}
@Override
public ContainerId createContainer(Container container) {
if (isNull(container)) {
throw new IllegalArgumentException("Container is not present");
}
var grpcContainer = ContainerMapper.toGrpcMessage(container);
grpcContainer = grpcContainer.toBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(getContext().getOwnerId()))
.setVersion(VersionMapper.toGrpcMessage(getContext().getVersion()))
.build();
var body = Service.PutRequest.Body.newBuilder()
.setContainer(grpcContainer)
.setSignature(RequestSigner.signRFC6979(getContext().getKey(), grpcContainer))
.build();
var request = Service.PutRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
RequestSigner.sign(request, getContext().getKey());
var response = serviceBlockingStub.put(request.build());
Verifier.checkResponse(response);
return new ContainerId(response.getBody().getContainerId().getValue().toByteArray());
}
@Override
public void deleteContainer(ContainerId cid) {
if (isNull(cid)) {
throw new IllegalArgumentException("ContainerId is not present");
}
var grpcContainerId = ContainerIdMapper.toGrpcMessage(cid);
var body = Service.DeleteRequest.Body.newBuilder()
.setContainerId(grpcContainerId)
.setSignature(RequestSigner.signRFC6979(getContext().getKey(), grpcContainerId.getValue()))
.build();
var request = Service.DeleteRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
RequestSigner.sign(request, getContext().getKey());
var response = serviceBlockingStub.delete(request.build());
Verifier.checkResponse(response);
}
}

View file

@ -0,0 +1,157 @@
package info.frostfs.sdk.services.impl;
import frostfs.netmap.NetmapServiceGrpc;
import frostfs.netmap.Service;
import frostfs.netmap.Types;
import info.frostfs.sdk.dto.netmap.NetmapSnapshot;
import info.frostfs.sdk.dto.netmap.NodeInfo;
import info.frostfs.sdk.jdo.ClientEnvironment;
import info.frostfs.sdk.jdo.NetworkSettings;
import info.frostfs.sdk.mappers.netmap.NetmapSnapshotMapper;
import info.frostfs.sdk.mappers.netmap.NodeInfoMapper;
import info.frostfs.sdk.services.ContextAccessor;
import info.frostfs.sdk.services.NetmapClient;
import info.frostfs.sdk.tools.RequestConstructor;
import info.frostfs.sdk.tools.Verifier;
import java.nio.charset.StandardCharsets;
import static info.frostfs.sdk.tools.RequestSigner.sign;
import static java.util.Objects.nonNull;
public class NetmapClientImpl extends ContextAccessor implements NetmapClient {
private final NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceClient;
public NetmapClientImpl(ClientEnvironment clientEnvironment) {
super(clientEnvironment);
this.netmapServiceClient = NetmapServiceGrpc.newBlockingStub(getContext().getChannel());
}
private static boolean getBoolValue(byte[] bytes) {
for (var byteValue : bytes) {
if (byteValue != 0) {
return true;
}
}
return false;
}
private static long getLongValue(byte[] bytes) {
long val = 0;
for (var i = bytes.length - 1; i >= 0; i--) {
val = (val << 8) + bytes[i];
}
return val;
}
private static void setNetworksParam(Types.NetworkConfig.Parameter param, NetworkSettings settings) {
var key = new String(param.getKey().toByteArray(), StandardCharsets.UTF_8);
var valueBytes = param.getValue().toByteArray();
switch (key) {
case "AuditFee":
settings.setAuditFee(getLongValue(valueBytes));
break;
case "BasicIncomeRate":
settings.setBasicIncomeRate(getLongValue(valueBytes));
break;
case "ContainerFee":
settings.setContainerFee(getLongValue(valueBytes));
break;
case "ContainerAliasFee":
settings.setContainerAliasFee(getLongValue(valueBytes));
break;
case "EpochDuration":
settings.setEpochDuration(getLongValue(valueBytes));
break;
case "InnerRingCandidateFee":
settings.setiRCandidateFee(getLongValue(valueBytes));
break;
case "MaxECDataCount":
settings.setMaxECDataCount(getLongValue(valueBytes));
break;
case "MaxECParityCount":
settings.setMaxECParityCount(getLongValue(valueBytes));
break;
case "MaxObjectSize":
settings.setMaxObjectSize(getLongValue(valueBytes));
break;
case "WithdrawFee":
settings.setWithdrawalFee(getLongValue(valueBytes));
break;
case "HomomorphicHashingDisabled":
settings.setHomomorphicHashingDisabled(getBoolValue(valueBytes));
break;
case "MaintenanceModeAllowed":
settings.setMaintenanceModeAllowed(getBoolValue(valueBytes));
break;
default:
settings.getUnnamedSettings().put(key, valueBytes);
break;
}
}
@Override
public NetworkSettings getNetworkSettings() {
if (nonNull(getContext().getNetworkSettings())) {
return getContext().getNetworkSettings();
}
var info = getNetworkInfo();
var settings = new NetworkSettings();
for (var param : info.getBody().getNetworkInfo().getNetworkConfig().getParametersList()) {
setNetworksParam(param, settings);
}
getContext().setNetworkSettings(settings);
return settings;
}
@Override
public NodeInfo getLocalNodeInfo() {
var request = Service.LocalNodeInfoRequest.newBuilder()
.setBody(Service.LocalNodeInfoRequest.Body.newBuilder().build());
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
var response = netmapServiceClient.localNodeInfo(request.build());
Verifier.checkResponse(response);
return NodeInfoMapper.toModel(response.getBody());
}
public Service.NetworkInfoResponse getNetworkInfo() {
var request = Service.NetworkInfoRequest.newBuilder()
.setBody(Service.NetworkInfoRequest.Body.newBuilder().build());
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
var response = netmapServiceClient.networkInfo(request.build());
Verifier.checkResponse(response);
return response;
}
@Override
public NetmapSnapshot getNetmapSnapshot() {
var request = Service.NetmapSnapshotRequest.newBuilder()
.setBody(Service.NetmapSnapshotRequest.Body.newBuilder().build());
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
var response = netmapServiceClient.netmapSnapshot(request.build());
Verifier.checkResponse(response);
return NetmapSnapshotMapper.toModel(response);
}
}

View file

@ -0,0 +1,367 @@
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.constants.AppConst;
import info.frostfs.sdk.dto.Split;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.dto.object.*;
import info.frostfs.sdk.jdo.ClientEnvironment;
import info.frostfs.sdk.jdo.PutObjectParameters;
import info.frostfs.sdk.mappers.OwnerIdMapper;
import info.frostfs.sdk.mappers.VersionMapper;
import info.frostfs.sdk.mappers.container.ContainerIdMapper;
import info.frostfs.sdk.mappers.object.ObjectFilterMapper;
import info.frostfs.sdk.mappers.object.ObjectFrostFSMapper;
import info.frostfs.sdk.mappers.object.ObjectHeaderMapper;
import info.frostfs.sdk.mappers.object.ObjectIdMapper;
import info.frostfs.sdk.services.ContextAccessor;
import info.frostfs.sdk.services.ObjectClient;
import info.frostfs.sdk.services.impl.rwhelper.ObjectReader;
import info.frostfs.sdk.services.impl.rwhelper.ObjectWriter;
import info.frostfs.sdk.services.impl.rwhelper.SearchReader;
import info.frostfs.sdk.tools.RequestConstructor;
import info.frostfs.sdk.tools.Verifier;
import org.apache.commons.collections4.CollectionUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static info.frostfs.sdk.Helper.getSha256;
import static info.frostfs.sdk.tools.RequestConstructor.addObjectSessionToken;
import static info.frostfs.sdk.tools.RequestSigner.sign;
import static java.util.Objects.nonNull;
public class ObjectClientImpl extends ContextAccessor implements ObjectClient {
private static final String ERROR_PAYLOAD = "PayloadLength must be specified";
private final ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceBlockingClient;
private final ObjectServiceGrpc.ObjectServiceStub objectServiceClient;
private final ObjectToolsImpl objectToolsImpl;
public ObjectClientImpl(ClientEnvironment clientEnvironment) {
super(clientEnvironment);
this.objectServiceBlockingClient = ObjectServiceGrpc.newBlockingStub(getContext().getChannel());
this.objectServiceClient = ObjectServiceGrpc.newStub(getContext().getChannel());
this.objectToolsImpl = new ObjectToolsImpl(clientEnvironment);
}
@Override
public ObjectHeader getObjectHead(ContainerId cid, ObjectId oid) {
var address = Types.Address.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setObjectId(ObjectIdMapper.toGrpcMessage(oid))
.build();
var body = Service.HeadRequest.Body.newBuilder()
.setAddress(address)
.build();
var request = Service.HeadRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
var response = objectServiceBlockingClient.head(request.build());
Verifier.checkResponse(response);
return ObjectHeaderMapper.toModel(response.getBody().getHeader().getHeader());
}
@Override
public ObjectFrostFS getObject(ContainerId cid, ObjectId oid) {
var sessionToken = getContext().getFrostFSClient().createSessionInternal(-1);
var address = Types.Address.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setObjectId(ObjectIdMapper.toGrpcMessage(oid))
.build();
var body = Service.GetRequest.Body.newBuilder()
.setAddress(address)
.build();
var request = Service.GetRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
addObjectSessionToken(
request, sessionToken, ContainerIdMapper.toGrpcMessage(cid), ObjectIdMapper.toGrpcMessage(oid),
frostfs.session.Types.ObjectSessionContext.Verb.GET, getContext().getKey()
);
sign(request, getContext().getKey());
var obj = getObject(request.build());
return ObjectFrostFSMapper.toModel(obj);
}
@Override
public void deleteObject(ContainerId cid, ObjectId oid) {
var address = Types.Address.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setObjectId(ObjectIdMapper.toGrpcMessage(oid))
.build();
var body = Service.DeleteRequest.Body.newBuilder()
.setAddress(address)
.build();
var request = Service.DeleteRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
var response = objectServiceBlockingClient.delete(request.build());
Verifier.checkResponse(response);
}
@Override
public Iterable<ObjectId> searchObjects(ContainerId cid, ObjectFilter... filters) {
var body = Service.SearchRequest.Body.newBuilder()
.setContainerId(ContainerIdMapper.toGrpcMessage(cid))
.setVersion(1);// TODO: clarify this param
for (ObjectFilter filter : filters) {
body.addFilters(ObjectFilterMapper.toGrpcMessage(filter));
}
var request = Service.SearchRequest.newBuilder()
.setBody(body.build());
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
var objectsIds = searchObjects(request.build());
return Iterables.transform(objectsIds, input -> new ObjectId(input.getValue().toByteArray()));
}
@Override
public ObjectId putObject(PutObjectParameters parameters) {
parameters.validate();
return parameters.clientCut ? putClientCutObject(parameters) : putStreamObject(parameters);
}
public ObjectId putSingleObject(ObjectFrostFS modelObject) {
var sessionToken = getContext().getFrostFSClient().createSessionInternal(-1);
var grpcObject = objectToolsImpl.createObject(modelObject);
var request = Service.PutSingleRequest.newBuilder()
.setBody(Service.PutSingleRequest.Body.newBuilder().setObject(grpcObject).build());
RequestConstructor.addDefaultMetaHeader(request);
addObjectSessionToken(
request, sessionToken, grpcObject.getHeader().getContainerId(), grpcObject.getObjectId(),
frostfs.session.Types.ObjectSessionContext.Verb.PUT, getContext().getKey()
);
sign(request, getContext().getKey());
var response = objectServiceBlockingClient.putSingle(request.build());
Verifier.checkResponse(response);
return new ObjectId(grpcObject.getObjectId().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 putStreamObject(PutObjectParameters parameters) {
var header = parameters.getHeader();
var sessionToken = getContext().getFrostFSClient().createSessionInternal(-1);
var hdr = ObjectHeaderMapper.toGrpcMessage(header);
hdr = hdr.toBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(getContext().getOwnerId()))
.setVersion(VersionMapper.toGrpcMessage(getContext().getVersion()))
.build();
var oid = Types.ObjectID.newBuilder().setValue(getSha256(hdr)).build();
var initRequest = Service.PutRequest.newBuilder()
.setBody(
Service.PutRequest.Body.newBuilder()
.setInit(
Service.PutRequest.Body.Init.newBuilder().setHeader(hdr).build()
).build()
);
RequestConstructor.addDefaultMetaHeader(initRequest);
addObjectSessionToken(
initRequest, sessionToken, hdr.getContainerId(), oid,
frostfs.session.Types.ObjectSessionContext.Verb.PUT, getContext().getKey()
);
sign(initRequest, getContext().getKey());
var writer = putObjectInit(initRequest.build());
var bufferSize = parameters.getBufferMaxSize() > 0 ? parameters.getBufferMaxSize() : AppConst.OBJECT_CHUNK_SIZE;
bufferSize = (int) Math.min(getStreamSize(parameters.getPayload()), bufferSize);
bufferSize = header.getPayloadLength() > 0 ? (int) Math.min(header.getPayloadLength(), bufferSize) : bufferSize;
var buffer = new byte[bufferSize];
while (true) {
var bytesCount = readNBytes(parameters.getPayload(), buffer, bufferSize);
if (bytesCount <= 0) {
break;
}
var chunkRequest = Service.PutRequest.newBuilder(initRequest.build())
.setBody(
Service.PutRequest.Body.newBuilder()
.setChunk(ByteString.copyFrom(Arrays.copyOfRange(buffer, 0, bytesCount)))
.build()
)
.clearVerifyHeader();
sign(chunkRequest, getContext().getKey());
writer.write(chunkRequest.build());
}
var response = writer.complete();
Verifier.checkResponse(response);
return new ObjectId(response.getBody().getObjectId().getValue().toByteArray());
}
private ObjectId putClientCutObject(PutObjectParameters parameters) {
var header = parameters.getHeader();
var networkSettings = getContext().getFrostFSClient().getNetworkSettings();
var payloadSize = getStreamSize(parameters.getPayload());
var objectSize = (int) Math.min(payloadSize, networkSettings.getMaxObjectSize());
var fullLength = header.getPayloadLength() == 0 ? payloadSize : header.getPayloadLength();
if (fullLength == 0) {
throw new IllegalArgumentException(ERROR_PAYLOAD);
}
var buffer = new byte[objectSize];
var largeObject = new LargeObject(header.getContainerId());
var split = new Split();
ObjectId objectId;
List<ObjectId> sentObjectIds = new ArrayList<>();
ObjectFrostFS currentObject;
while (true) {
var bytesCount = readNBytes(parameters.getPayload(), buffer, objectSize);
if (CollectionUtils.isNotEmpty(sentObjectIds)) {
split.setPrevious(sentObjectIds.get(sentObjectIds.size() - 1));
}
largeObject.appendBlock(buffer, bytesCount);
currentObject = new ObjectFrostFS(
header.getContainerId(),
bytesCount < objectSize ? Arrays.copyOfRange(buffer, 0, bytesCount) : buffer
);
currentObject.setSplit(split);
if (largeObject.getPayloadLength() == fullLength) {
break;
}
objectId = putSingleObject(currentObject);
sentObjectIds.add(objectId);
}
if (CollectionUtils.isEmpty(sentObjectIds)) {
currentObject.addAttributes(parameters.getHeader().getAttributes());
return putSingleObject(currentObject);
}
largeObject.addAttributes(parameters.getHeader().getAttributes());
largeObject.calculateHash();
currentObject.setParent(largeObject);
objectId = putSingleObject(currentObject);
sentObjectIds.add(objectId);
var linkObject = new LinkObject(header.getContainerId(), split.getSplitId(), largeObject);
linkObject.addChildren(sentObjectIds);
linkObject.getHeader().getAttributes().clear();
putSingleObject(linkObject);
return objectToolsImpl.calculateObjectId(largeObject.getHeader());
}
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));
}
private int readNBytes(FileInputStream fileInputStream, byte[] buffer, int size) {
try {
return fileInputStream.readNBytes(buffer, 0, size);
} catch (IOException exp) {
throw new IllegalArgumentException(exp.getMessage());
}
}
private long getStreamSize(FileInputStream fileInputStream) {
try {
return fileInputStream.getChannel().size();
} catch (IOException exp) {
throw new IllegalArgumentException(exp.getMessage());
}
}
}

View file

@ -0,0 +1,104 @@
package info.frostfs.sdk.services.impl;
import com.google.protobuf.ByteString;
import frostfs.object.Types;
import info.frostfs.sdk.dto.object.ObjectFrostFS;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.dto.object.ObjectId;
import info.frostfs.sdk.jdo.ClientEnvironment;
import info.frostfs.sdk.mappers.OwnerIdMapper;
import info.frostfs.sdk.mappers.VersionMapper;
import info.frostfs.sdk.mappers.object.ObjectHeaderMapper;
import info.frostfs.sdk.mappers.object.ObjectIdMapper;
import info.frostfs.sdk.services.ContextAccessor;
import info.frostfs.sdk.services.ToolsClient;
import org.apache.commons.collections4.ListUtils;
import static info.frostfs.sdk.Helper.getSha256;
import static info.frostfs.sdk.tools.RequestSigner.signData;
import static java.util.Objects.nonNull;
public class ObjectToolsImpl extends ContextAccessor implements ToolsClient {
public ObjectToolsImpl(ClientEnvironment context) {
super(context);
}
private static frostfs.refs.Types.Checksum sha256Checksum(byte[] data) {
return frostfs.refs.Types.Checksum.newBuilder()
.setType(frostfs.refs.Types.ChecksumType.SHA256)
.setSum(ByteString.copyFrom(getSha256(data)))
.build();
}
@Override
public ObjectId calculateObjectId(ObjectHeader header) {
var grpcHeader = createHeader(header, new byte[]{});
return ObjectIdMapper.toModel(
frostfs.refs.Types.ObjectID.newBuilder().setValue(getSha256(grpcHeader)).build()
);
}
public Types.Object createObject(ObjectFrostFS objectFrostFs) {
var grpcHeaderBuilder = ObjectHeaderMapper.toGrpcMessage(objectFrostFs.getHeader()).toBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(getContext().getOwnerId()))
.setVersion(VersionMapper.toGrpcMessage(getContext().getVersion()))
.setPayloadLength(objectFrostFs.getPayload().length)
.setPayloadHash(sha256Checksum(objectFrostFs.getPayload()));
var split = objectFrostFs.getHeader().getSplit();
if (nonNull(split)) {
var splitGrpc = Types.Header.Split.newBuilder()
.setSplitId(nonNull(split.getSplitId()) ? ByteString.copyFrom(split.getSplitId().toBinary()) : null);
ListUtils.emptyIfNull(split.getChildren()).stream()
.map(ObjectIdMapper::toGrpcMessage)
.forEach(splitGrpc::addChildren);
if (nonNull(split.getParentHeader())) {
var grpcParentHeader = createHeader(split.getParentHeader(), new byte[]{});
var parent = frostfs.refs.Types.ObjectID.newBuilder().setValue(getSha256(grpcParentHeader)).build();
var parentSig = frostfs.refs.Types.Signature.newBuilder()
.setKey(ByteString.copyFrom(getContext().getKey().getPublicKeyByte()))
.setSign(ByteString.copyFrom(signData(getContext().getKey(), parent.toByteArray())));
splitGrpc.setParent(parent)
.setParentHeader(grpcParentHeader)
.setParentSignature(parentSig);
split.setParent(ObjectIdMapper.toModel(parent));
}
if (nonNull(split.getPrevious())) {
splitGrpc.setPrevious(ObjectIdMapper.toGrpcMessage(split.getPrevious()));
}
grpcHeaderBuilder.setSplit(splitGrpc);
}
var grpcHeader = grpcHeaderBuilder.build();
var objectId = frostfs.refs.Types.ObjectID.newBuilder().setValue(getSha256(grpcHeader)).build();
var sig = frostfs.refs.Types.Signature.newBuilder()
.setKey(ByteString.copyFrom(getContext().getKey().getPublicKeyByte()))
.setSign(ByteString.copyFrom(signData(getContext().getKey(), objectId.toByteArray())));
return Types.Object.newBuilder()
.setHeader(grpcHeader)
.setObjectId(objectId)
.setPayload(ByteString.copyFrom(objectFrostFs.getPayload()))
.setSignature(sig)
.build();
}
private Types.Header createHeader(ObjectHeader header, byte[] payload) {
var grpcHeader = ObjectHeaderMapper.toGrpcMessage(header).toBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(getContext().getOwnerId()))
.setVersion(VersionMapper.toGrpcMessage(getContext().getVersion()));
if (header.getPayloadCheckSum() != null) {
grpcHeader.setPayloadHash(sha256Checksum(header.getPayloadCheckSum()));
} else if (payload != null) {
grpcHeader.setPayloadHash(sha256Checksum(payload));
}
return grpcHeader.build();
}
}

View file

@ -0,0 +1,65 @@
package info.frostfs.sdk.services.impl;
import frostfs.session.Service;
import frostfs.session.SessionServiceGrpc;
import frostfs.session.Types;
import info.frostfs.sdk.dto.SessionToken;
import info.frostfs.sdk.jdo.ClientEnvironment;
import info.frostfs.sdk.mappers.OwnerIdMapper;
import info.frostfs.sdk.mappers.SessionMapper;
import info.frostfs.sdk.services.ContextAccessor;
import info.frostfs.sdk.services.SessionClient;
import info.frostfs.sdk.tools.RequestConstructor;
import static info.frostfs.sdk.tools.RequestSigner.sign;
public class SessionClientImpl extends ContextAccessor implements SessionClient {
private final SessionServiceGrpc.SessionServiceBlockingStub serviceBlockingStub;
public SessionClientImpl(ClientEnvironment clientEnvironment) {
super(clientEnvironment);
this.serviceBlockingStub = SessionServiceGrpc.newBlockingStub(getContext().getChannel());
}
@Override
public SessionToken createSession(long expiration) {
var sessionToken = createSessionInternal(expiration);
var token = SessionMapper.serialize(sessionToken);
return new SessionToken(new byte[]{}, token);
}
public Types.SessionToken createSessionInternal(long expiration) {
var body = Service.CreateRequest.Body.newBuilder()
.setOwnerId(OwnerIdMapper.toGrpcMessage(getContext().getOwnerId()))
.setExpiration(expiration)
.build();
var request = Service.CreateRequest.newBuilder()
.setBody(body);
RequestConstructor.addDefaultMetaHeader(request);
sign(request, getContext().getKey());
return createSession(request.build());
}
private Types.SessionToken createSession(Service.CreateRequest request) {
var resp = serviceBlockingStub.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();
}
}

View file

@ -0,0 +1,51 @@
package info.frostfs.sdk.services.impl.rwhelper;
import frostfs.object.Service;
import frostfs.object.Types;
import info.frostfs.sdk.tools.Verifier;
import java.util.Iterator;
public class ObjectReader {
public static final String ERROR_UNEXPECTED_STREAM = "unexpected end of stream";
public static final String ERROR_UNEXPECTED_MESSAGE_TYPE = "unexpected message type";
public Iterator<Service.GetResponse> call;
public ObjectReader(Iterator<Service.GetResponse> call) {
this.call = call;
}
public Types.Object readHeader() {
if (!call.hasNext()) {
throw new IllegalArgumentException(ERROR_UNEXPECTED_STREAM);
}
var response = call.next();
Verifier.checkResponse(response);
if (response.getBody().getObjectPartCase().getNumber() != Service.GetResponse.Body.INIT_FIELD_NUMBER) {
throw new IllegalArgumentException(ERROR_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(ERROR_UNEXPECTED_MESSAGE_TYPE);
}
return response.getBody().getChunk().toByteArray();
}
}

View file

@ -0,0 +1,59 @@
package info.frostfs.sdk.services.impl.rwhelper;
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.rwhelper;
import frostfs.object.Service;
import info.frostfs.sdk.tools.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,29 @@
package info.frostfs.sdk.tools;
import io.grpc.Channel;
import io.grpc.ChannelCredentials;
import io.grpc.netty.NettyChannelBuilder;
import java.net.URI;
import java.net.URISyntaxException;
import static java.util.Objects.isNull;
public class GrpcClient {
private static final String ERROR_INVALID_HOST_TEMPLATE = "Host %s has invalid format. Error: %s";
private GrpcClient() {
}
public static Channel initGrpcChannel(String host, ChannelCredentials creds) {
try {
URI uri = new URI(host);
var channelBuilder = isNull(creds) ? NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort())
: NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort(), creds);
return channelBuilder.usePlaintext().build();
} catch (URISyntaxException exp) {
throw new IllegalArgumentException(String.format(ERROR_INVALID_HOST_TEMPLATE, host, exp.getMessage()));
}
}
}

View file

@ -0,0 +1,18 @@
package info.frostfs.sdk.tools;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
public class MessageHelper {
private MessageHelper() {
}
public static Message getField(MessageOrBuilder messageOrBuilder, String fieldName) {
return (Message) messageOrBuilder.getField(messageOrBuilder.getDescriptorForType().findFieldByName(fieldName));
}
public static void setField(Message.Builder builder, String fieldName, Object value) {
builder.setField(builder.getDescriptorForType().findFieldByName(fieldName), value);
}
}

View file

@ -0,0 +1,63 @@
package info.frostfs.sdk.tools;
import com.google.protobuf.Message;
import frostfs.session.Types;
import info.frostfs.sdk.dto.MetaHeader;
import info.frostfs.sdk.jdo.ECDsa;
import info.frostfs.sdk.mappers.MetaHeaderMapper;
import static info.frostfs.sdk.constants.FieldConst.META_HEADER_FIELD_NAME;
import static info.frostfs.sdk.tools.MessageHelper.getField;
import static info.frostfs.sdk.tools.MessageHelper.setField;
import static info.frostfs.sdk.tools.RequestSigner.signMessagePart;
import static java.util.Objects.isNull;
public class RequestConstructor {
private RequestConstructor() {
}
public static void addDefaultMetaHeader(Message.Builder request) {
addMetaHeader(request, null);
}
public static void addMetaHeader(Message.Builder request, Types.RequestMetaHeader metaHeader) {
if (isNull(request)) {
return;
}
if (isNull(metaHeader) || metaHeader.getSerializedSize() == 0) {
metaHeader = MetaHeaderMapper.toGrpcMessage(new MetaHeader());
setField(request, META_HEADER_FIELD_NAME, metaHeader);
}
}
public static void addObjectSessionToken(Message.Builder request,
Types.SessionToken sessionToken,
frostfs.refs.Types.ContainerID cid,
frostfs.refs.Types.ObjectID oid,
Types.ObjectSessionContext.Verb verb,
ECDsa key) {
if (isNull(request) || isNull(sessionToken)) {
return;
}
var header = (Types.RequestMetaHeader) getField(request, META_HEADER_FIELD_NAME);
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(key, body))
.setBody(body)
.build();
setField(request, META_HEADER_FIELD_NAME, header.toBuilder().setSessionToken(sessionToken).build());
}
}

View file

@ -0,0 +1,121 @@
package info.frostfs.sdk.tools;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import frostfs.session.Types;
import info.frostfs.sdk.constants.CryptoConst;
import info.frostfs.sdk.jdo.ECDsa;
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.constants.FieldConst.*;
import static org.bouncycastle.crypto.util.DigestFactory.createSHA256;
import static org.bouncycastle.util.BigIntegers.asUnsignedByteArray;
public class RequestSigner {
public static final String ERROR_UNSUPPORTED_TYPE_TEMPLATE = "Unsupported message type: %s";
public static final int RFC6979_SIGNATURE_SIZE = 64;
private RequestSigner() {
}
public static byte[] signData(ECDsa key, byte[] data) {
var hash = new byte[65];
hash[0] = 0x04;
try {
Signature signature = Signature.getInstance(CryptoConst.SIGNATURE_ALGORITHM);
signature.initSign(key.getPrivateKey());
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(ECDsa key, 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, key.getPrivateKeyByte()), 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(ECDsa key, Message message) {
return frostfs.refs.Types.SignatureRFC6979.newBuilder()
.setKey(ByteString.copyFrom(key.getPublicKeyByte()))
.setSign(ByteString.copyFrom(signRFC6979(key, message.toByteArray())))
.build();
}
public static frostfs.refs.Types.SignatureRFC6979 signRFC6979(ECDsa key, ByteString data) {
return frostfs.refs.Types.SignatureRFC6979.newBuilder()
.setKey(ByteString.copyFrom(key.getPublicKeyByte()))
.setSign(ByteString.copyFrom(signRFC6979(key, data.toByteArray())))
.build();
}
public static frostfs.refs.Types.Signature signMessagePart(ECDsa key, Message data) {
var data2Sign = data.getSerializedSize() == 0 ? new byte[]{} : data.toByteArray();
return frostfs.refs.Types.Signature.newBuilder()
.setKey(ByteString.copyFrom(key.getPublicKeyByte()))
.setSign(ByteString.copyFrom(signData(key, data2Sign)))
.build();
}
public static void sign(Message.Builder request, ECDsa key) {
var meta = MessageHelper.getField(request, META_HEADER_FIELD_NAME);
var body = MessageHelper.getField(request, BODY_FIELD_NAME);
var verify = MessageHelper.getField(request, VERIFY_HEADER_FIELD_NAME);
var verifyOrigin = MessageHelper.getField(verify, ORIGIN_FIELD_NAME);
Message.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(
String.format(ERROR_UNSUPPORTED_TYPE_TEMPLATE, verify.getClass().getName())
);
}
if (verifyOrigin.getSerializedSize() == 0) {
MessageHelper.setField(verifyBuilder, BODY_SIGNATURE_FIELD_NAME, signMessagePart(key, body));
} else {
MessageHelper.setField(verifyBuilder, ORIGIN_FIELD_NAME, verifyOrigin);
}
MessageHelper.setField(verifyBuilder, META_SIGNATURE_FIELD_NAME, signMessagePart(key, meta));
MessageHelper.setField(verifyBuilder, ORIGIN_SIGNATURE_FIELD_NAME, signMessagePart(key, verifyOrigin));
MessageHelper.setField(request, VERIFY_HEADER_FIELD_NAME, verifyBuilder.build());
}
}

View file

@ -0,0 +1,130 @@
package info.frostfs.sdk.tools;
import com.google.protobuf.Message;
import frostfs.session.Types;
import info.frostfs.sdk.constants.CryptoConst;
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.getPublicKeyFromBytes;
import static info.frostfs.sdk.constants.FieldConst.*;
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 String ERROR_WRONG_SIG_SIZE_TEMPLATE = "Wrong signature size. Expected length=%s, actual=%s";
public static final String ERROR_INVALID_RESPONSE = "Invalid response";
public static final int RFC6979_SIG_SIZE = 64;
private Verifier() {
}
public static boolean verifyRFC6979(frostfs.refs.Types.SignatureRFC6979 signature, Message data) {
return verifyRFC6979(signature.getKey().toByteArray(), data.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_SIG_SIZE) {
throw new IllegalArgumentException(
String.format(ERROR_WRONG_SIG_SIZE_TEMPLATE, RFC6979_SIG_SIZE, sig.length)
);
}
var rs = new BigInteger[2];
rs[0] = fromUnsignedByteArray(Arrays.copyOfRange(sig, 0, (RFC6979_SIG_SIZE / 2) - 1));
rs[1] = fromUnsignedByteArray(Arrays.copyOfRange(sig, RFC6979_SIG_SIZE / 2, RFC6979_SIG_SIZE - 1));
return rs;
}
public static void checkResponse(Message response) {
if (!verify(response)) {
throw new IllegalArgumentException(ERROR_INVALID_RESPONSE);
}
var metaHeader = (Types.ResponseMetaHeader) MessageHelper.getField(response, META_HEADER_FIELD_NAME);
var status = StatusMapper.toModel(metaHeader.getStatus());
if (!status.isSuccess()) {
throw new IllegalArgumentException(status.toString());
}
}
public static boolean verify(Message response) {
var body = MessageHelper.getField(response, BODY_FIELD_NAME);
var metaHeader = (Types.ResponseMetaHeader) MessageHelper.getField(response, META_HEADER_FIELD_NAME);
var verifyHeader = (Types.ResponseVerificationHeader) MessageHelper.getField(response, VERIFY_HEADER_FIELD_NAME);
return verifyMatryoshkaLevel(body, metaHeader, verifyHeader);
}
public static boolean verifyMatryoshkaLevel(Message data,
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(), data);
}
return verification.getBodySignature().getSerializedSize() == 0
&& verifyMatryoshkaLevel(data, meta.getOrigin(), origin);
}
public static boolean verifyMessagePart(frostfs.refs.Types.Signature sig, Message data) {
if (sig.getSerializedSize() == 0 || sig.getKey().isEmpty() || sig.getSign().isEmpty()) {
return false;
}
var publicKey = getPublicKeyFromBytes(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(CryptoConst.SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
signature.update(DigestUtils.sha512(data));
return signature.verify(Arrays.copyOfRange(sig, 1, sig.length));
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
}

39
cryptography/pom.xml Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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>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.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,14 @@
package info.frostfs.sdk;
public class ArrayHelper {
private 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,135 @@
package info.frostfs.sdk;
import org.apache.commons.lang3.StringUtils;
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;
}
}
private Base58() {
}
public static byte[] base58CheckDecode(String input) {
if (StringUtils.isEmpty(input)) {
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));
var bufferEnd = Arrays.copyOfRange(buffer, buffer.length - 4, buffer.length);
var checksumStart = Arrays.copyOfRange(checksum, 0, 4);
if (!Arrays.equals(bufferEnd, checksumStart)) {
throw new IllegalArgumentException();
}
return decode;
}
public static String base58CheckEncode(byte[] data) {
if (isNull(data)) {
throw new IllegalArgumentException("Input value is missing");
}
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,60 @@
package info.frostfs.sdk;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import static java.util.Objects.isNull;
public class Helper {
private Helper() {
}
public static byte[] getRIPEMD160(byte[] value) {
if (isNull(value)) {
throw new IllegalArgumentException("Input value is missing");
}
var hash = new byte[20];
var digest = new RIPEMD160Digest();
digest.update(value, 0, value.length);
digest.doFinal(hash, 0);
return hash;
}
public static MessageDigest getSha256Instance() {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] getSha256(byte[] value) {
if (isNull(value)) {
throw new IllegalArgumentException("Input value is missing");
}
return getSha256Instance().digest(value);
}
public static ByteString getSha256(Message value) {
if (isNull(value)) {
throw new IllegalArgumentException("Input value is missing");
}
return ByteString.copyFrom(getSha256(value.toByteArray()));
}
public static String getHexString(byte[] value) {
if (isNull(value)) {
throw new IllegalArgumentException("Input value is missing");
}
return String.format("%0" + (value.length << 1) + "x", new BigInteger(1, value));
}
}

View file

@ -0,0 +1,192 @@
package info.frostfs.sdk;
import org.apache.commons.lang3.StringUtils;
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 java.util.Objects.isNull;
import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray;
public class KeyExtension {
public static final byte NEO_ADDRESS_VERSION = 0x35;
private static final int DECODE_ADDRESS_LENGTH = 21;
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();
private KeyExtension() {
}
public static byte[] compress(byte[] publicKey) {
checkInputValue(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) {
if (StringUtils.isEmpty(wif)) {
throw new IllegalArgumentException("Input value is missing");
}
var data = Base58.base58CheckDecode(wif);
return Arrays.copyOfRange(data, 1, data.length - 1);
}
public static byte[] loadPublicKey(byte[] privateKey) {
checkInputValue(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) {
checkInputValue(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 getPublicKeyFromBytes(byte[] publicKey) {
checkInputValue(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) {
checkInputValue(publicKey);
var script = createSignatureRedeemScript(publicKey);
return getRIPEMD160(getSha256(script));
}
public static String publicKeyToAddress(byte[] publicKey) {
checkInputValue(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) {
checkInputValue(scriptHash);
byte[] data = new byte[DECODE_ADDRESS_LENGTH];
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) {
checkInputValue(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;
}
private static void checkInputValue(byte[] data) {
if (isNull(data)) {
throw new IllegalArgumentException("Input value is missing");
}
}
}

43
models/pom.xml Normal file
View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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>models</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>protos</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,35 @@
package info.frostfs.sdk;
import java.nio.ByteBuffer;
import java.util.UUID;
import static java.util.Objects.isNull;
public class UUIDExtension {
private static final int UUID_BYTE_ARRAY_LENGTH = 16;
private UUIDExtension() {
}
public static UUID asUuid(byte[] bytes) {
if (isNull(bytes) || bytes.length != UUID_BYTE_ARRAY_LENGTH) {
throw new IllegalArgumentException("Uuid byte array length must be " + UUID_BYTE_ARRAY_LENGTH);
}
ByteBuffer bb = ByteBuffer.wrap(bytes);
long firstLong = bb.getLong();
long secondLong = bb.getLong();
return new UUID(firstLong, secondLong);
}
public static byte[] asBytes(UUID uuid) {
if (isNull(uuid)) {
throw new IllegalArgumentException("Uuid is not present");
}
ByteBuffer bb = ByteBuffer.allocate(16);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
}

View file

@ -0,0 +1,11 @@
package info.frostfs.sdk.constants;
public class AppConst {
public static final int DEFAULT_MAJOR_VERSION = 2;
public static final int DEFAULT_MINOR_VERSION = 13;
public static final int OBJECT_CHUNK_SIZE = 3 * (1 << 20);
public static final int SHA256_HASH_LENGTH = 32;
private AppConst() {
}
}

View file

@ -0,0 +1,15 @@
package info.frostfs.sdk.constants;
public class FieldConst {
public static final String META_HEADER_FIELD_NAME = "meta_header";
public static final String META_SIGNATURE_FIELD_NAME = "meta_signature";
public static final String BODY_FIELD_NAME = "body";
public static final String BODY_SIGNATURE_FIELD_NAME = "body_signature";
public static final String ORIGIN_FIELD_NAME = "origin";
public static final String ORIGIN_SIGNATURE_FIELD_NAME = "origin_signature";
public static final String VERIFY_HEADER_FIELD_NAME = "verify_header";
public static final String EMPTY_STRING = "";
private FieldConst() {
}
}

View file

@ -0,0 +1,10 @@
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";
private XHeaderConst() {
}
}

View file

@ -0,0 +1,63 @@
package info.frostfs.sdk.dto;
import static info.frostfs.sdk.constants.AppConst.DEFAULT_MAJOR_VERSION;
import static info.frostfs.sdk.constants.AppConst.DEFAULT_MINOR_VERSION;
import static java.util.Objects.isNull;
public class MetaHeader {
private Version version;
private int epoch;
private int ttl;
public MetaHeader(Version version, int epoch, int ttl) {
if (isNull(version) || epoch < 0 || ttl <= 0) {
throw new IllegalArgumentException("One of the input attributes is invalid or missing");
}
this.version = version;
this.epoch = epoch;
this.ttl = ttl;
}
public MetaHeader() {
this.version = new Version(DEFAULT_MAJOR_VERSION, DEFAULT_MINOR_VERSION);
this.epoch = 0;
this.ttl = 2;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
if (isNull(version)) {
throw new IllegalArgumentException("Version is missing.");
}
this.version = version;
}
public int getEpoch() {
return epoch;
}
public void setEpoch(int epoch) {
if (epoch < 0) {
throw new IllegalArgumentException("The epoch must be greater than or equal to zero.");
}
this.epoch = epoch;
}
public int getTtl() {
return ttl;
}
public void setTtl(int ttl) {
if (ttl <= 0) {
throw new IllegalArgumentException("The ttl must be greater than zero.");
}
this.ttl = ttl;
}
}

View file

@ -0,0 +1,30 @@
package info.frostfs.sdk.dto;
import info.frostfs.sdk.Base58;
import static info.frostfs.sdk.KeyExtension.publicKeyToAddress;
import static java.util.Objects.isNull;
public class OwnerId {
private final String value;
public OwnerId(String value) {
this.value = value;
}
public OwnerId(byte[] publicKey) {
if (isNull(publicKey) || publicKey.length == 0) {
throw new IllegalArgumentException("PublicKey is invalid");
}
this.value = publicKeyToAddress(publicKey);
}
public String getValue() {
return value;
}
public byte[] toHash() {
return Base58.decode(value);
}
}

View file

@ -0,0 +1,19 @@
package info.frostfs.sdk.dto;
public class SessionToken {
private final byte[] id;
private final byte[] sessionKey;
public SessionToken(byte[] id, byte[] sessionKey) {
this.id = id;
this.sessionKey = sessionKey;
}
public byte[] getId() {
return id;
}
public byte[] getSessionKey() {
return sessionKey;
}
}

View file

@ -0,0 +1,33 @@
package info.frostfs.sdk.dto;
import info.frostfs.sdk.enums.SignatureScheme;
public class Signature {
private byte[] key;
private byte[] sign;
private SignatureScheme scheme;
public byte[] getKey() {
return key;
}
public void setKey(byte[] key) {
this.key = key;
}
public byte[] getSign() {
return sign;
}
public void setSign(byte[] sign) {
this.sign = sign;
}
public SignatureScheme getScheme() {
return scheme;
}
public void setScheme(SignatureScheme scheme) {
this.scheme = scheme;
}
}

View file

@ -0,0 +1,71 @@
package info.frostfs.sdk.dto;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.dto.object.ObjectId;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.isNull;
public class Split {
private final List<ObjectId> children;
private final SplitId splitId;
private ObjectId parent;
private ObjectId previous;
private Signature parentSignature;
private ObjectHeader parentHeader;
public Split() {
this(new SplitId());
}
public Split(SplitId splitId) {
if (isNull(splitId)) {
throw new IllegalArgumentException("SplitId is not present");
}
this.splitId = splitId;
this.children = new ArrayList<>();
}
public SplitId getSplitId() {
return splitId;
}
public ObjectId getParent() {
return parent;
}
public void setParent(ObjectId parent) {
this.parent = parent;
}
public ObjectId getPrevious() {
return previous;
}
public void setPrevious(ObjectId previous) {
this.previous = previous;
}
public Signature getParentSignature() {
return parentSignature;
}
public void setParentSignature(Signature parentSignature) {
this.parentSignature = parentSignature;
}
public ObjectHeader getParentHeader() {
return parentHeader;
}
public void setParentHeader(ObjectHeader parentHeader) {
this.parentHeader = parentHeader;
}
public List<ObjectId> getChildren() {
return children;
}
}

View file

@ -0,0 +1,36 @@
package info.frostfs.sdk.dto;
import java.util.UUID;
import static info.frostfs.sdk.UUIDExtension.asBytes;
import static info.frostfs.sdk.UUIDExtension.asUuid;
import static java.util.Objects.isNull;
public class SplitId {
private final UUID id;
public SplitId() {
this.id = UUID.randomUUID();
}
public SplitId(UUID uuid) {
this.id = uuid;
}
public SplitId(byte[] binary) {
this.id = asUuid(binary);
}
public SplitId(String str) {
this.id = UUID.fromString(str);
}
@Override
public String toString() {
return id.toString();
}
public byte[] toBinary() {
return isNull(id) ? null : asBytes(id);
}
}

View file

@ -0,0 +1,46 @@
package info.frostfs.sdk.dto;
import info.frostfs.sdk.enums.StatusCode;
import static info.frostfs.sdk.constants.FieldConst.EMPTY_STRING;
import static java.util.Objects.isNull;
public class Status {
private StatusCode code;
private String message;
public Status(StatusCode code, String message) {
this.code = code;
this.message = isNull(message) ? EMPTY_STRING : message;
}
public Status(StatusCode code) {
this.code = code;
this.message = EMPTY_STRING;
}
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 String.format("Response status: %s. Message: %s.", code, message);
}
public boolean isSuccess() {
return StatusCode.SUCCESS.equals(code);
}
}

View file

@ -0,0 +1,41 @@
package info.frostfs.sdk.dto;
import static info.frostfs.sdk.constants.AppConst.DEFAULT_MAJOR_VERSION;
import static info.frostfs.sdk.constants.AppConst.DEFAULT_MINOR_VERSION;
import static java.util.Objects.isNull;
public class Version {
private final int major;
private final int minor;
public Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
public Version() {
this.major = DEFAULT_MAJOR_VERSION;
this.minor = DEFAULT_MINOR_VERSION;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
@Override
public String toString() {
return "v" + major + "." + minor;
}
public boolean isSupported(Version version) {
if (isNull(version)) {
return false;
}
return major == version.getMajor();
}
}

View file

@ -0,0 +1,52 @@
package info.frostfs.sdk.dto.container;
import info.frostfs.sdk.dto.Version;
import info.frostfs.sdk.dto.netmap.PlacementPolicy;
import info.frostfs.sdk.enums.BasicAcl;
import java.util.UUID;
public class Container {
private UUID nonce;
private BasicAcl basicAcl;
private PlacementPolicy placementPolicy;
private Version version;
public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) {
this.nonce = UUID.randomUUID();
this.basicAcl = basicAcl;
this.placementPolicy = placementPolicy;
}
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;
}
}

View file

@ -0,0 +1,40 @@
package info.frostfs.sdk.dto.container;
import info.frostfs.sdk.Base58;
import info.frostfs.sdk.constants.AppConst;
import org.apache.commons.lang3.StringUtils;
import static java.util.Objects.isNull;
public class ContainerId {
private final String value;
public ContainerId(String value) {
if (StringUtils.isEmpty(value)) {
throw new IllegalArgumentException("ContainerId value is missing");
}
this.value = value;
}
public ContainerId(byte[] hash) {
if (isNull(hash) || hash.length != AppConst.SHA256_HASH_LENGTH) {
throw new IllegalArgumentException("ContainerId must be a sha256 hash.");
}
this.value = 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,21 @@
package info.frostfs.sdk.dto.netmap;
import java.util.List;
public class NetmapSnapshot {
private final Long epoch;
private final List<NodeInfo> nodeInfoCollection;
public NetmapSnapshot(Long epoch, List<NodeInfo> nodeInfoCollection) {
this.epoch = epoch;
this.nodeInfoCollection = nodeInfoCollection;
}
public Long getEpoch() {
return epoch;
}
public List<NodeInfo> getNodeInfoCollection() {
return nodeInfoCollection;
}
}

View file

@ -0,0 +1,44 @@
package info.frostfs.sdk.dto.netmap;
import info.frostfs.sdk.dto.Version;
import info.frostfs.sdk.enums.NodeState;
import java.util.List;
import java.util.Map;
public class NodeInfo {
private final NodeState state;
private final Version version;
private final List<String> addresses;
private final Map<String, String> attributes;
private final byte[] publicKey;
public NodeInfo(NodeState state, Version version, List<String> addresses,
Map<String, String> attributes, byte[] publicKey) {
this.state = state;
this.version = version;
this.addresses = addresses;
this.attributes = attributes;
this.publicKey = publicKey;
}
public NodeState getState() {
return state;
}
public Version getVersion() {
return version;
}
public byte[] getPublicKey() {
return publicKey;
}
public Map<String, String> getAttributes() {
return attributes;
}
public List<String> getAddresses() {
return addresses;
}
}

View file

@ -0,0 +1,19 @@
package info.frostfs.sdk.dto.netmap;
public class PlacementPolicy {
private final Replica[] replicas;
private final boolean unique;
public PlacementPolicy(boolean unique, Replica[] replicas) {
this.replicas = replicas;
this.unique = unique;
}
public Replica[] getReplicas() {
return replicas;
}
public boolean isUnique() {
return unique;
}
}

View file

@ -0,0 +1,36 @@
package info.frostfs.sdk.dto.netmap;
import org.apache.commons.lang3.StringUtils;
import static info.frostfs.sdk.constants.FieldConst.EMPTY_STRING;
public class Replica {
private final int count;
private final String selector;
public Replica(int count, String selector) {
if (count <= 0) {
throw new IllegalArgumentException("Replica count must be positive");
}
this.count = count;
this.selector = StringUtils.isEmpty(selector) ? EMPTY_STRING : selector;
}
public Replica(int count) {
if (count <= 0) {
throw new IllegalArgumentException("Replica count must be positive");
}
this.count = count;
this.selector = EMPTY_STRING;
}
public int getCount() {
return count;
}
public String getSelector() {
return selector;
}
}

View file

@ -0,0 +1,34 @@
package info.frostfs.sdk.dto.object;
import info.frostfs.sdk.dto.container.ContainerId;
import java.security.MessageDigest;
import static info.frostfs.sdk.Helper.getSha256Instance;
import static java.util.Objects.isNull;
public class LargeObject extends ObjectFrostFS {
private final MessageDigest payloadHash;
public LargeObject(ContainerId cid) {
super(cid, new byte[]{});
this.payloadHash = getSha256Instance();
}
public void appendBlock(byte[] bytes, int count) {
if (count == 0 || isNull(bytes) || bytes.length == 0) {
return;
}
this.getHeader().increasePayloadLength(count);
this.payloadHash.update(bytes, 0, count);
}
public void calculateHash() {
this.getHeader().setPayloadCheckSum(this.payloadHash.digest());
}
public long getPayloadLength() {
return getHeader().getPayloadLength();
}
}

View file

@ -0,0 +1,26 @@
package info.frostfs.sdk.dto.object;
import info.frostfs.sdk.dto.Split;
import info.frostfs.sdk.dto.SplitId;
import info.frostfs.sdk.dto.container.ContainerId;
import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
public class LinkObject extends ObjectFrostFS {
public LinkObject(ContainerId cid, SplitId splitId, LargeObject largeObject) {
super(cid, new byte[]{});
var split = new Split(splitId);
split.setParentHeader(largeObject.getHeader());
this.getHeader().setSplit(split);
}
public void addChildren(List<ObjectId> objectIds) {
if (CollectionUtils.isEmpty(objectIds)) {
return;
}
this.getHeader().getSplit().getChildren().addAll(objectIds);
}
}

View file

@ -0,0 +1,25 @@
package info.frostfs.sdk.dto.object;
import org.apache.commons.lang3.StringUtils;
public class ObjectAttribute {
private final String key;
private final String value;
public ObjectAttribute(String key, String value) {
if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
throw new IllegalArgumentException("One of the input attributes is missing");
}
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}

View file

@ -0,0 +1,60 @@
package info.frostfs.sdk.dto.object;
import info.frostfs.sdk.dto.OwnerId;
import info.frostfs.sdk.dto.Version;
import info.frostfs.sdk.enums.ObjectMatchType;
public class ObjectFilter {
private static final String HEADER_PREFIX = "$Object:";
private ObjectMatchType matchType;
private String key;
private 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.getValue());
}
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,80 @@
package info.frostfs.sdk.dto.object;
import info.frostfs.sdk.dto.Split;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.enums.ObjectType;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.isNull;
public class ObjectFrostFS {
private final ObjectHeader header;
private ObjectId objectId;
private byte[] payload;
public ObjectFrostFS(ObjectHeader header, ObjectId objectId, byte[] payload) {
if (isNull(header)) {
throw new IllegalArgumentException("Object header is missing");
}
this.header = header;
this.objectId = objectId;
this.payload = payload;
}
public ObjectFrostFS(ContainerId containerId, byte[] payload) {
this.payload = payload;
this.header = new ObjectHeader(containerId, new ArrayList<>());
}
public ObjectFrostFS(ContainerId containerId, byte[] payload, ObjectType objectType) {
this.payload = payload;
this.header = new ObjectHeader(containerId, objectType, new ArrayList<>());
}
public ObjectHeader getHeader() {
return header;
}
public ObjectId getObjectId() {
return objectId;
}
private void setObjectId(ObjectId objectId) {
this.objectId = objectId;
}
public byte[] getPayload() {
return payload;
}
public void setPayload(byte[] payload) {
this.payload = payload;
}
public void setSplit(Split split) {
header.setSplit(split);
}
public void addAttribute(String key, String value) {
header.getAttributes().add(new ObjectAttribute(key, value));
}
public void addAttribute(ObjectAttribute attribute) {
header.getAttributes().add(attribute);
}
public void addAttributes(List<ObjectAttribute> attributes) {
header.getAttributes().addAll(attributes);
}
public void setParent(LargeObject largeObject) {
if (isNull(header.getSplit())) {
throw new IllegalArgumentException("The object is not initialized properly");
}
header.getSplit().setParentHeader(largeObject.getHeader());
}
}

View file

@ -0,0 +1,128 @@
package info.frostfs.sdk.dto.object;
import info.frostfs.sdk.dto.OwnerId;
import info.frostfs.sdk.dto.Split;
import info.frostfs.sdk.dto.Version;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.enums.ObjectType;
import java.util.List;
import static java.util.Objects.isNull;
public class ObjectHeader {
private final ContainerId containerId;
private final ObjectType objectType;
private List<ObjectAttribute> attributes;
private long size;
private Version version;
private OwnerId ownerId;
private long payloadLength;
private byte[] payloadCheckSum;
private Split split;
public ObjectHeader(ContainerId containerId, ObjectType objectType,
List<ObjectAttribute> attributes, long size, Version version) {
if (isNull(containerId) || isNull(objectType)) {
throw new IllegalArgumentException("ContainerId or objectType is not present");
}
this.attributes = attributes;
this.containerId = containerId;
this.size = size;
this.objectType = objectType;
this.version = version;
}
public ObjectHeader(ContainerId containerId, ObjectType objectType, List<ObjectAttribute> attributes) {
if (isNull(containerId) || isNull(objectType)) {
throw new IllegalArgumentException("ContainerId or objectType is not present");
}
this.attributes = attributes;
this.containerId = containerId;
this.objectType = objectType;
}
public ObjectHeader(ContainerId containerId, List<ObjectAttribute> attributes) {
if (isNull(containerId)) {
throw new IllegalArgumentException("ContainerId is not present");
}
this.attributes = attributes;
this.containerId = containerId;
this.objectType = ObjectType.REGULAR;
}
public OwnerId getOwnerId() {
return ownerId;
}
public void setOwnerId(OwnerId ownerId) {
this.ownerId = ownerId;
}
public long getPayloadLength() {
return payloadLength;
}
public void setPayloadLength(long payloadLength) {
this.payloadLength = payloadLength;
}
public void increasePayloadLength(long payloadLength) {
this.payloadLength += payloadLength;
}
public byte[] getPayloadCheckSum() {
return payloadCheckSum;
}
public void setPayloadCheckSum(byte[] payloadCheckSum) {
this.payloadCheckSum = payloadCheckSum;
}
public Split getSplit() {
return split;
}
public void setSplit(Split split) {
if (isNull(split)) {
throw new IllegalArgumentException("Split is not present");
}
this.split = split;
}
public List<ObjectAttribute> getAttributes() {
return attributes;
}
public void setAttributes(List<ObjectAttribute> attributes) {
this.attributes = attributes;
}
public ContainerId getContainerId() {
return containerId;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public ObjectType getObjectType() {
return objectType;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
}

View file

@ -0,0 +1,40 @@
package info.frostfs.sdk.dto.object;
import info.frostfs.sdk.Base58;
import info.frostfs.sdk.constants.AppConst;
import org.apache.commons.lang3.StringUtils;
import static java.util.Objects.isNull;
public class ObjectId {
private final String value;
public ObjectId(String value) {
if (StringUtils.isEmpty(value)) {
throw new IllegalArgumentException("ObjectId value is missing");
}
this.value = value;
}
public ObjectId(byte[] hash) {
if (isNull(hash) || hash.length != AppConst.SHA256_HASH_LENGTH) {
throw new IllegalArgumentException("ObjectId must be a sha256 hash.");
}
this.value = 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,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,14 @@
package info.frostfs.sdk.enums;
public enum SignatureScheme {
ECDSA_SHA512(0),
ECDSA_RFC6979_SHA256(1),
ECDSA_RFC6979_SHA256_WALLET_CONNECT(2),
;
public final int value;
SignatureScheme(int value) {
this.value = 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,24 @@
package info.frostfs.sdk.mappers;
import frostfs.session.Types;
import info.frostfs.sdk.dto.MetaHeader;
import static java.util.Objects.isNull;
public class MetaHeaderMapper {
private MetaHeaderMapper() {
}
public static Types.RequestMetaHeader toGrpcMessage(MetaHeader metaHeader) {
if (isNull(metaHeader)) {
return null;
}
return Types.RequestMetaHeader.newBuilder()
.setVersion(VersionMapper.toGrpcMessage(metaHeader.getVersion()))
.setEpoch(metaHeader.getEpoch())
.setTtl(metaHeader.getTtl())
.build();
}
}

View file

@ -0,0 +1,23 @@
package info.frostfs.sdk.mappers;
import com.google.protobuf.ByteString;
import frostfs.refs.Types;
import info.frostfs.sdk.dto.OwnerId;
import static java.util.Objects.isNull;
public class OwnerIdMapper {
private OwnerIdMapper() {
}
public static Types.OwnerID toGrpcMessage(OwnerId ownerId) {
if (isNull(ownerId)) {
return null;
}
return Types.OwnerID.newBuilder()
.setValue(ByteString.copyFrom(ownerId.toHash()))
.build();
}
}

View file

@ -0,0 +1,42 @@
package info.frostfs.sdk.mappers;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import frostfs.session.Types;
import java.io.IOException;
import static java.util.Objects.isNull;
public class SessionMapper {
private SessionMapper() {
}
public static byte[] serialize(Types.SessionToken token) {
if (isNull(token)) {
throw new IllegalArgumentException("Token is not present");
}
try {
byte[] bytes = new byte[token.getSerializedSize()];
CodedOutputStream stream = CodedOutputStream.newInstance(bytes);
token.writeTo(stream);
return bytes;
} catch (IOException exp) {
throw new IllegalArgumentException(exp.getMessage());
}
}
public static Types.SessionToken deserializeSessionToken(byte[] bytes) {
if (isNull(bytes) || bytes.length == 0) {
throw new IllegalArgumentException("Token is not present");
}
try {
return Types.SessionToken.newBuilder().mergeFrom(bytes).build();
} catch (InvalidProtocolBufferException exp) {
throw new IllegalArgumentException(exp.getMessage());
}
}
}

View file

@ -0,0 +1,32 @@
package info.frostfs.sdk.mappers;
import com.google.protobuf.ByteString;
import frostfs.refs.Types;
import info.frostfs.sdk.dto.Signature;
import static java.util.Objects.isNull;
public class SignatureMapper {
private SignatureMapper() {
}
public static Types.Signature toGrpcMessage(Signature signature) {
if (isNull(signature)) {
return null;
}
var scheme = Types.SignatureScheme.forNumber(signature.getScheme().value);
if (isNull(scheme)) {
throw new IllegalArgumentException(
String.format("Unknown SignatureScheme. Value: %s.", signature.getScheme().name())
);
}
return Types.Signature.newBuilder()
.setKey(ByteString.copyFrom(signature.getKey()))
.setSign(ByteString.copyFrom(signature.getSign()))
.setScheme(scheme)
.build();
}
}

View file

@ -0,0 +1,28 @@
package info.frostfs.sdk.mappers;
import frostfs.status.Types;
import info.frostfs.sdk.dto.Status;
import info.frostfs.sdk.enums.StatusCode;
import static java.util.Objects.isNull;
public class StatusMapper {
private 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,31 @@
package info.frostfs.sdk.mappers;
import frostfs.refs.Types;
import info.frostfs.sdk.dto.Version;
import static java.util.Objects.isNull;
public class VersionMapper {
private VersionMapper() {
}
public static Types.Version toGrpcMessage(Version version) {
if (isNull(version)) {
return null;
}
return Types.Version.newBuilder()
.setMajor(version.getMajor())
.setMinor(version.getMinor())
.build();
}
public static Version toModel(Types.Version version) {
if (isNull(version)) {
return null;
}
return new Version(version.getMajor(), version.getMinor());
}
}

View file

@ -0,0 +1,23 @@
package info.frostfs.sdk.mappers.container;
import com.google.protobuf.ByteString;
import frostfs.refs.Types;
import info.frostfs.sdk.dto.container.ContainerId;
import static java.util.Objects.isNull;
public class ContainerIdMapper {
private ContainerIdMapper() {
}
public static Types.ContainerID toGrpcMessage(ContainerId containerId) {
if (isNull(containerId)) {
return null;
}
return Types.ContainerID.newBuilder()
.setValue(ByteString.copyFrom(containerId.toHash()))
.build();
}
}

View file

@ -0,0 +1,48 @@
package info.frostfs.sdk.mappers.container;
import com.google.protobuf.ByteString;
import frostfs.container.Types;
import info.frostfs.sdk.dto.container.Container;
import info.frostfs.sdk.enums.BasicAcl;
import info.frostfs.sdk.mappers.VersionMapper;
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 {
private ContainerMapper() {
}
public static Types.Container toGrpcMessage(Container container) {
if (isNull(container)) {
return null;
}
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 containerGrpc) {
if (isNull(containerGrpc)) {
return null;
}
var basicAcl = BasicAcl.get(containerGrpc.getBasicAcl());
if (isNull(basicAcl)) {
throw new IllegalArgumentException(
String.format("Unknown BasicACL rule. Value: %s.", containerGrpc.getBasicAcl())
);
}
var container = new Container(basicAcl, PlacementPolicyMapper.toModel(containerGrpc.getPlacementPolicy()));
container.setNonce(asUuid(containerGrpc.getNonce().toByteArray()));
container.setVersion(VersionMapper.toModel(containerGrpc.getVersion()));
return container;
}
}

View file

@ -0,0 +1,27 @@
package info.frostfs.sdk.mappers.netmap;
import frostfs.netmap.Service;
import info.frostfs.sdk.dto.netmap.NetmapSnapshot;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
public class NetmapSnapshotMapper {
private NetmapSnapshotMapper() {
}
public static NetmapSnapshot toModel(Service.NetmapSnapshotResponse netmap) {
if (isNull(netmap)) {
return null;
}
return new NetmapSnapshot(
netmap.getBody().getNetmap().getEpoch(),
netmap.getBody().getNetmap().getNodesList().stream()
.map(node -> NodeInfoMapper.toModel(node, netmap.getMetaHeader().getVersion()))
.collect(Collectors.toList())
);
}
}

View file

@ -0,0 +1,47 @@
package info.frostfs.sdk.mappers.netmap;
import frostfs.netmap.Service;
import frostfs.netmap.Types.NodeInfo.Attribute;
import frostfs.refs.Types;
import info.frostfs.sdk.dto.netmap.NodeInfo;
import info.frostfs.sdk.enums.NodeState;
import info.frostfs.sdk.mappers.VersionMapper;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
public class NodeInfoMapper {
private NodeInfoMapper() {
}
public static NodeInfo toModel(Service.LocalNodeInfoResponse.Body nodeInfo) {
if (isNull(nodeInfo)) {
return null;
}
return toModel(nodeInfo.getNodeInfo(), nodeInfo.getVersion());
}
public static NodeInfo toModel(frostfs.netmap.Types.NodeInfo nodeInfo, Types.Version version) {
if (isNull(nodeInfo)) {
return null;
}
NodeState nodeState = NodeState.get(nodeInfo.getState().getNumber());
if (isNull(nodeState)) {
throw new IllegalArgumentException(
String.format("Unknown NodeState. Value: %s.", nodeInfo.getState())
);
}
return new NodeInfo(
nodeState,
VersionMapper.toModel(version),
nodeInfo.getAddressesList(),
nodeInfo.getAttributesList().stream().collect(Collectors.toMap(Attribute::getKey, Attribute::getValue)),
nodeInfo.getPublicKey().toByteArray()
);
}
}

View file

@ -0,0 +1,39 @@
package info.frostfs.sdk.mappers.netmap;
import frostfs.netmap.Types;
import info.frostfs.sdk.dto.netmap.PlacementPolicy;
import info.frostfs.sdk.dto.netmap.Replica;
import static java.util.Objects.isNull;
public class PlacementPolicyMapper {
private PlacementPolicyMapper() {
}
public static Types.PlacementPolicy toGrpcMessage(PlacementPolicy placementPolicy) {
if (isNull(placementPolicy)) {
return null;
}
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) {
if (isNull(placementPolicy)) {
return null;
}
return new PlacementPolicy(
placementPolicy.getUnique(),
placementPolicy.getReplicasList().stream().map(ReplicaMapper::toModel).toArray(Replica[]::new)
);
}
}

View file

@ -0,0 +1,31 @@
package info.frostfs.sdk.mappers.netmap;
import frostfs.netmap.Types;
import info.frostfs.sdk.dto.netmap.Replica;
import static java.util.Objects.isNull;
public class ReplicaMapper {
private ReplicaMapper() {
}
public static Types.Replica toGrpcMessage(Replica replica) {
if (isNull(replica)) {
return null;
}
return Types.Replica.newBuilder()
.setCount(replica.getCount())
.setSelector(replica.getSelector())
.build();
}
public static Replica toModel(Types.Replica replica) {
if (isNull(replica)) {
return null;
}
return new Replica(replica.getCount(), replica.getSelector());
}
}

View file

@ -0,0 +1,31 @@
package info.frostfs.sdk.mappers.object;
import frostfs.object.Types;
import info.frostfs.sdk.dto.object.ObjectAttribute;
import static java.util.Objects.isNull;
public class ObjectAttributeMapper {
private ObjectAttributeMapper() {
}
public static Types.Header.Attribute toGrpcMessage(ObjectAttribute attribute) {
if (isNull(attribute)) {
return null;
}
return Types.Header.Attribute.newBuilder()
.setKey(attribute.getKey())
.setValue(attribute.getValue())
.build();
}
public static ObjectAttribute toModel(Types.Header.Attribute attribute) {
if (isNull(attribute)) {
return null;
}
return new ObjectAttribute(attribute.getKey(), attribute.getValue());
}
}

View file

@ -0,0 +1,32 @@
package info.frostfs.sdk.mappers.object;
import frostfs.object.Service;
import frostfs.object.Types;
import info.frostfs.sdk.dto.object.ObjectFilter;
import static java.util.Objects.isNull;
public class ObjectFilterMapper {
private ObjectFilterMapper() {
}
public static Service.SearchRequest.Body.Filter toGrpcMessage(ObjectFilter filter) {
if (isNull(filter)) {
return null;
}
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,25 @@
package info.frostfs.sdk.mappers.object;
import frostfs.object.Types;
import info.frostfs.sdk.dto.object.ObjectFrostFS;
import info.frostfs.sdk.dto.object.ObjectId;
import static java.util.Objects.isNull;
public class ObjectFrostFSMapper {
private ObjectFrostFSMapper() {
}
public static ObjectFrostFS toModel(Types.Object object) {
if (isNull(object)) {
return null;
}
return new ObjectFrostFS(
ObjectHeaderMapper.toModel(object.getHeader()),
new ObjectId(object.getObjectId().getValue().toByteArray()),
object.getPayload().toByteArray()
);
}
}

View file

@ -0,0 +1,63 @@
package info.frostfs.sdk.mappers.object;
import frostfs.object.Types;
import info.frostfs.sdk.dto.container.ContainerId;
import info.frostfs.sdk.dto.object.ObjectAttribute;
import info.frostfs.sdk.dto.object.ObjectHeader;
import info.frostfs.sdk.enums.ObjectType;
import info.frostfs.sdk.mappers.VersionMapper;
import info.frostfs.sdk.mappers.container.ContainerIdMapper;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
public class ObjectHeaderMapper {
private ObjectHeaderMapper() {
}
public static Types.Header toGrpcMessage(ObjectHeader header) {
if (isNull(header)) {
return null;
}
var objectType = Types.ObjectType.forNumber(header.getObjectType().value);
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) {
if (isNull(header)) {
return null;
}
var objectType = ObjectType.get(header.getObjectTypeValue());
if (isNull(objectType)) {
throw new IllegalArgumentException(
String.format("Unknown ObjectType. Value: %s.", header.getObjectType())
);
}
return new ObjectHeader(
new ContainerId(header.getContainerId().getValue().toByteArray()),
objectType,
header.getAttributesList().stream().map(ObjectAttributeMapper::toModel).collect(Collectors.toList()),
header.getPayloadLength(),
VersionMapper.toModel(header.getVersion())
);
}
}

View file

@ -0,0 +1,27 @@
package info.frostfs.sdk.mappers.object;
import com.google.protobuf.ByteString;
import frostfs.refs.Types;
import info.frostfs.sdk.dto.object.ObjectId;
import static java.util.Objects.isNull;
public class ObjectIdMapper {
private ObjectIdMapper() {
}
public static Types.ObjectID toGrpcMessage(ObjectId objectId) {
if (isNull(objectId)) {
return null;
}
return Types.ObjectID.newBuilder()
.setValue(ByteString.copyFrom(objectId.toHash()))
.build();
}
public static ObjectId toModel(Types.ObjectID objectId) {
return new ObjectId(objectId.getValue().toByteArray());
}
}

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>client</module>
<module>cryptography</module>
<module>models</module>
<module>protos</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
protos/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>protos</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.65.1</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;
}

Some files were not shown because too many files have changed in this diff Show more