diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3f0616 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6538ca1 --- /dev/null +++ b/CONTRIBUTING.md @@ -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//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 +`/-` 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] Summary + +Description + + + + +``` + +``` +$ 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 +``` + +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. +``` \ No newline at end of file diff --git a/README.md b/README.md index 9f37b2a..26697ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,94 @@ # frostfs-sdk-java Java implementation of FrostFS SDK + +## Prerequisites + +### Get the key for your wallet + +1. Get the address +```bash +cat | jq .accounts[0].address | tr -d '"' +``` + +2. Get the key +```bash +neo-go wallet export -w -d +``` + +## Example usage + +### Container + +```java +import info.FrostFS.sdk.enums.BasicAcl; +import info.FrostFS.sdk.jdo.Container; +import info.FrostFS.sdk.jdo.netmap.PlacementPolicy; +import info.FrostFS.sdk.jdo.netmap.Replica; +import info.FrostFS.sdk.services.impl.FrostFSClient; + +public class ContainerExample { + + public void example() { + Client client = new Client( < your_key >); + GrpcClient grpcClient = new GrpcClient( < your_host >); + FrostFSClient frostFSClient = new FrostFSClient(grpcClient, client); + + // Create container + var placementPolicy = new PlacementPolicy(true, new Replica[]{new Replica(1)}); + var containerId = frostFSClient.createContainerAsync(new Container(BasicAcl.PUBLIC_RW, placementPolicy)); + + // Get container + var container = frostFSClient.getContainerAsync(containerId); + + // List containers + var containerIds = frostFSClient.listContainersAsync(); + + // Delete container + frostFSClient.deleteContainerAsync(containerId); + } +} +``` + +### Object + +```java +import info.FrostFS.sdk.enums.ObjectType; +import info.FrostFS.sdk.jdo.ContainerId; +import info.FrostFS.sdk.jdo.ObjectAttribute; +import info.FrostFS.sdk.jdo.ObjectFilter; +import info.FrostFS.sdk.jdo.ObjectHeader; +import info.FrostFS.sdk.services.impl.FrostFSClient; + +import java.io.FileInputStream; +import java.io.IOException; + +public class ObjectExample { + + public void example() { + Client client = new Client( < your_key >); + GrpcClient grpcClient = new GrpcClient( < your_host >); + FrostFSClient frostFSClient = new FrostFSClient(grpcClient, client); + + // Put object + info.FrostFS.sdk.jdo.ObjectId objectId; + try (FileInputStream fis = new FileInputStream("cat.jpg")) { + var cat = new ObjectHeader( + containerId, ObjectType.REGULAR, new ObjectAttribute[]{new ObjectAttribute("Filename", "cat.jpg")} + ); + objectId = frostFSClient.putObjectAsync(cat, fis); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Get object + var obj = frostFSClient.getObjectAsync(containerId, objectId); + + // Get object header + var objectHeader = frostFSClient.getObjectHeadAsync(containerId, objectId); + + // Search regular objects + var objectIds = frostFSClient.searchObjectsAsync(containerId, ObjectFilter.RootFilter()); + } +} +``` \ No newline at end of file diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 0000000..f748035 --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + info.FrostFS.sdk + FrostFS-sdk-java + 0.1.0 + + + client + + + 11 + 11 + UTF-8 + + + + + info.FrostFS.sdk + cryptography + 0.1.0 + + + info.FrostFS.sdk + protosV2 + 0.1.0 + + + info.FrostFS.sdk + modelsV2 + 0.1.0 + + + org.apache.logging.log4j + log4j-api + 2.7 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.7 + + + + \ No newline at end of file diff --git a/client/src/main/java/info/FrostFS/sdk/Client.java b/client/src/main/java/info/FrostFS/sdk/Client.java new file mode 100644 index 0000000..2d254b9 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/Client.java @@ -0,0 +1,36 @@ +package info.FrostFS.sdk; + +import info.FrostFS.sdk.jdo.OwnerId; +import info.FrostFS.sdk.jdo.Version; + +import static info.FrostFS.sdk.KeyExtension.getPrivateKeyFromWIF; +import static info.FrostFS.sdk.KeyExtension.loadPublicKey; + +public class Client { + private final OwnerId ownerId; + private final Version version = new Version(2, 13); + private final byte[] privateKey; + private final byte[] publicKey; + + public Client(String key) { + this.privateKey = getPrivateKeyFromWIF(key); + this.publicKey = loadPublicKey(privateKey); + this.ownerId = OwnerId.fromKey(publicKey); + } + + public OwnerId getOwnerId() { + return ownerId; + } + + public Version getVersion() { + return version; + } + + public byte[] getPrivateKey() { + return privateKey; + } + + public byte[] getPublicKey() { + return publicKey; + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/GrpcClient.java b/client/src/main/java/info/FrostFS/sdk/GrpcClient.java new file mode 100644 index 0000000..0b32277 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/GrpcClient.java @@ -0,0 +1,89 @@ +package info.FrostFS.sdk; + +import frostFS.container.ContainerServiceGrpc; +import frostFS.netmap.NetmapServiceGrpc; +import frostFS.object.ObjectServiceGrpc; +import frostFS.session.SessionServiceGrpc; +import io.grpc.Channel; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyChannelBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLException; +import java.net.URI; +import java.net.URISyntaxException; + +public class GrpcClient { + private static final Logger log = LoggerFactory.getLogger(GrpcClient.class); + + private final ContainerServiceGrpc.ContainerServiceBlockingStub containerServiceBlockingClient; + private final NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceBlockingClient; + private final ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceBlockingClient; + private final ObjectServiceGrpc.ObjectServiceStub objectServiceClient; + private final SessionServiceGrpc.SessionServiceBlockingStub sessionServiceBlockingClient; + + public GrpcClient(String host) { + Channel channel = initGrpcChannel(host); + this.containerServiceBlockingClient = ContainerServiceGrpc.newBlockingStub(channel); + this.netmapServiceBlockingClient = NetmapServiceGrpc.newBlockingStub(channel); + this.objectServiceBlockingClient = ObjectServiceGrpc.newBlockingStub(channel); + this.objectServiceClient = ObjectServiceGrpc.newStub(channel); + this.sessionServiceBlockingClient = SessionServiceGrpc.newBlockingStub(channel); + } + + public static Channel initGrpcChannel(String host) { + URI uri; + try { + uri = new URI(host); + } catch (URISyntaxException exp) { + var message = String.format("Host %s has invalid format. Error: %s", host, exp.getMessage()); + log.error(message); + throw new IllegalArgumentException(message); + } + + var channelBuilder = NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort()) + .usePlaintext(); + + switch (uri.getScheme()) { + case "https": + try { + channelBuilder.sslContext( + GrpcSslContexts.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build() + ); + } catch (SSLException e) { + throw new RuntimeException(e); + } + break; + case "http": + break; + default: + var message = String.format("Host %s has invalid URI scheme: %s", host, uri.getScheme()); + log.error(message); + throw new IllegalArgumentException(message); + } + + return channelBuilder.build(); + } + + public ContainerServiceGrpc.ContainerServiceBlockingStub getContainerServiceBlockingClient() { + return containerServiceBlockingClient; + } + + public NetmapServiceGrpc.NetmapServiceBlockingStub getNetmapServiceBlockingClient() { + return netmapServiceBlockingClient; + } + + public ObjectServiceGrpc.ObjectServiceBlockingStub getObjectServiceBlockingClient() { + return objectServiceBlockingClient; + } + + public ObjectServiceGrpc.ObjectServiceStub getObjectServiceClient() { + return objectServiceClient; + } + + public SessionServiceGrpc.SessionServiceBlockingStub getSessionServiceBlockingClient() { + return sessionServiceBlockingClient; + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/RequestConstructor.java b/client/src/main/java/info/FrostFS/sdk/RequestConstructor.java new file mode 100644 index 0000000..304a878 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/RequestConstructor.java @@ -0,0 +1,50 @@ +package info.FrostFS.sdk; + +import com.google.protobuf.AbstractMessage; +import frostFS.session.Types; +import info.FrostFS.sdk.jdo.MetaHeader; +import info.FrostFS.sdk.mappers.MetaHeaderMapper; + +import static info.FrostFS.sdk.RequestSigner.signMessagePart; +import static java.util.Objects.isNull; + +public class RequestConstructor { + + public static void addMetaHeader(AbstractMessage.Builder request) { + addMetaHeader(request, null); + } + + public static void addMetaHeader(AbstractMessage.Builder request, Types.RequestMetaHeader metaHeader) { + if (isNull(metaHeader) || metaHeader.getSerializedSize() == 0) { + metaHeader = MetaHeaderMapper.toGrpcMessage(MetaHeader.getDefault()); + var field = request.getDescriptorForType().findFieldByName("meta_header"); + request.setField(field, metaHeader); + } + } + + public static void addObjectSessionToken(AbstractMessage.Builder request, + Types.SessionToken sessionToken, + frostFS.refs.Types.ContainerID cid, + frostFS.refs.Types.ObjectID oid, + Types.ObjectSessionContext.Verb verb, + byte[] publicKey, byte[] privateKey) { + var headerField = request.getDescriptorForType().findFieldByName("meta_header"); + var header = (Types.RequestMetaHeader) request.getField(headerField); + if (header.getSessionToken().getSerializedSize() > 0) { + return; + } + + var ctx = Types.ObjectSessionContext.newBuilder() + .setTarget(Types.ObjectSessionContext.Target.newBuilder().setContainer(cid).addObjects(oid).build()) + .setVerb(verb) + .build(); + + var body = sessionToken.getBody().toBuilder().setObject(ctx).build(); + sessionToken = sessionToken.toBuilder() + .setSignature(signMessagePart(publicKey, privateKey, body)) + .setBody(body) + .build(); + + request.setField(headerField, header.toBuilder().setSessionToken(sessionToken).build()); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/RequestSigner.java b/client/src/main/java/info/FrostFS/sdk/RequestSigner.java new file mode 100644 index 0000000..264d52a --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/RequestSigner.java @@ -0,0 +1,114 @@ +package info.FrostFS.sdk; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ByteString; +import frostFS.session.Types; +import org.apache.commons.codec.digest.DigestUtils; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; + +import java.math.BigInteger; +import java.security.Signature; + +import static info.FrostFS.sdk.KeyExtension.loadPrivateKey; +import static org.bouncycastle.crypto.util.DigestFactory.createSHA256; +import static org.bouncycastle.util.BigIntegers.asUnsignedByteArray; + +public class RequestSigner { + public static final int RFC6979_SIGNATURE_SIZE = 64; + + public static byte[] signData(byte[] privateKey, byte[] data) { + var hash = new byte[65]; + hash[0] = 0x04; + try { + Signature signature = Signature.getInstance("NONEwithECDSAinP1363Format"); + signature.initSign(loadPrivateKey(privateKey)); + signature.update(DigestUtils.sha512(data)); + byte[] sig = signature.sign(); + System.arraycopy(sig, 0, hash, 1, sig.length); + } catch (Exception exp) { + throw new RuntimeException(exp); + } + + return hash; + } + + public static byte[] signRFC6979(byte[] privateKey, byte[] data) { + var digest = createSHA256(); + var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); + + var ecParameters = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN()); + var ecPrivateKey = new ECPrivateKeyParameters(new BigInteger(1, privateKey), ecParameters); + var signer = new ECDSASigner(new HMacDSAKCalculator(digest)); + var hash = new byte[digest.getDigestSize()]; + + digest.update(data, 0, data.length); + digest.doFinal(hash, 0); + signer.init(true, ecPrivateKey); + + var rs = signer.generateSignature(hash); + var rBytes = asUnsignedByteArray(rs[0]); + var sBytes = asUnsignedByteArray(rs[1]); + + var signature = new byte[RFC6979_SIGNATURE_SIZE]; + var index = RFC6979_SIGNATURE_SIZE / 2 - rBytes.length; + System.arraycopy(rBytes, 0, signature, index, rBytes.length); + index = RFC6979_SIGNATURE_SIZE - sBytes.length; + System.arraycopy(sBytes, 0, signature, index, sBytes.length); + return signature; + } + + public static frostFS.refs.Types.SignatureRFC6979 signRFC6979(byte[] publicKey, byte[] privateKey, AbstractMessage message) { + return frostFS.refs.Types.SignatureRFC6979.newBuilder() + .setKey(ByteString.copyFrom(publicKey)) + .setSign(ByteString.copyFrom(signRFC6979(privateKey, message.toByteArray()))) + .build(); + } + + public static frostFS.refs.Types.SignatureRFC6979 signRFC6979(byte[] publicKey, byte[] privateKey, ByteString data) { + return frostFS.refs.Types.SignatureRFC6979.newBuilder() + .setKey(ByteString.copyFrom(publicKey)) + .setSign(ByteString.copyFrom(signRFC6979(privateKey, data.toByteArray()))) + .build(); + } + + public static frostFS.refs.Types.Signature signMessagePart(byte[] publicKey, byte[] privateKey, AbstractMessage data) { + var data2Sign = data.getSerializedSize() == 0 ? new byte[]{} : data.toByteArray(); + + return frostFS.refs.Types.Signature.newBuilder() + .setKey(ByteString.copyFrom(publicKey)) + .setSign(ByteString.copyFrom(signData(privateKey, data2Sign))) + .build(); + } + + public static void sign(AbstractMessage.Builder request, byte[] publicKey, byte[] privateKey) { + var meta = (AbstractMessage) request.getField(request.getDescriptorForType().findFieldByName("meta_header")); + var body = (AbstractMessage) request.getField(request.getDescriptorForType().findFieldByName("body")); + var verify = (AbstractMessage) request.getField(request.getDescriptorForType().findFieldByName("verify_header")); + + AbstractMessage.Builder verifyBuilder; + if (verify instanceof Types.RequestVerificationHeader) { + verifyBuilder = Types.RequestVerificationHeader.newBuilder(); + } else if (verify instanceof Types.ResponseVerificationHeader) { + verifyBuilder = Types.ResponseVerificationHeader.newBuilder(); + } else { + throw new IllegalArgumentException("Unsopported message type"); + } + + var verifyOrigin = (AbstractMessage) verify.getField(verify.getDescriptorForType().findFieldByName("origin")); + if (verifyOrigin.getSerializedSize() == 0) { + verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("body_signature"), signMessagePart(publicKey, privateKey, body)); + } else { + verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("origin"), verifyOrigin); + } + + verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("meta_signature"), signMessagePart(publicKey, privateKey, meta)); + verifyBuilder.setField(verifyBuilder.getDescriptorForType().findFieldByName("origin_signature"), signMessagePart(publicKey, privateKey, verifyOrigin)); + + request.setField(request.getDescriptorForType().findFieldByName("verify_header"), verifyBuilder.build()); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/Verifier.java b/client/src/main/java/info/FrostFS/sdk/Verifier.java new file mode 100644 index 0000000..657be82 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/Verifier.java @@ -0,0 +1,116 @@ +package info.FrostFS.sdk; + +import com.google.protobuf.AbstractMessage; +import frostFS.refs.Types; +import info.FrostFS.sdk.mappers.StatusMapper; +import org.apache.commons.codec.digest.DigestUtils; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; + +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.Signature; +import java.util.Arrays; + +import static info.FrostFS.sdk.KeyExtension.getPublicKeyByPublic; +import static java.util.Objects.isNull; +import static org.bouncycastle.crypto.util.DigestFactory.createSHA256; +import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray; + +public class Verifier { + public static final int RFC6979_SIGNATURE_SIZE = 64; + + public static boolean verifyRFC6979(Types.SignatureRFC6979 signature, AbstractMessage message) { + return verifyRFC6979(signature.getKey().toByteArray(), message.toByteArray(), signature.getSign().toByteArray()); + } + + public static boolean verifyRFC6979(byte[] publicKey, byte[] data, byte[] sig) { + if (isNull(publicKey) || isNull(data) || isNull(sig)) return false; + + var rs = decodeSignature(sig); + var digest = createSHA256(); + var signer = new ECDSASigner(new HMacDSAKCalculator(digest)); + var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); + var ecParameters = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN()); + var ecPublicKey = new ECPublicKeyParameters(secp256R1.getCurve().decodePoint(publicKey), ecParameters); + var hash = new byte[digest.getDigestSize()]; + digest.update(data, 0, data.length); + digest.doFinal(hash, 0); + signer.init(false, ecPublicKey); + return signer.verifySignature(hash, rs[0], rs[1]); + } + + private static BigInteger[] decodeSignature(byte[] sig) { + if (sig.length != RFC6979_SIGNATURE_SIZE) { + throw new IllegalArgumentException( + String.format("Wrong signature size. Expected length=%s, actual=%s", + RFC6979_SIGNATURE_SIZE, sig.length) + ); + } + + var rs = new BigInteger[2]; + + rs[0] = fromUnsignedByteArray(Arrays.copyOfRange(sig, 0, (RFC6979_SIGNATURE_SIZE / 2) - 1)); + rs[1] = fromUnsignedByteArray(Arrays.copyOfRange(sig, RFC6979_SIGNATURE_SIZE / 2, RFC6979_SIGNATURE_SIZE - 1)); + return rs; + } + + public static void checkResponse(AbstractMessage response) { + if (!verify(response)) { + throw new IllegalArgumentException("Invalid response"); + } + var metaHeader = (frostFS.session.Types.ResponseMetaHeader) response + .getField(response.getDescriptorForType().findFieldByName("meta_header")); + var status = StatusMapper.toModel(metaHeader.getStatus()); + if (!status.isSuccess()) { + throw new IllegalArgumentException(status.toString()); + } + } + + public static boolean verify(AbstractMessage message) { + var body = (AbstractMessage) message.getField(message.getDescriptorForType().findFieldByName("body")); + var metaHeader = (frostFS.session.Types.ResponseMetaHeader) + message.getField(message.getDescriptorForType().findFieldByName("meta_header")); + var verifyHeader = (frostFS.session.Types.ResponseVerificationHeader) + message.getField(message.getDescriptorForType().findFieldByName("verify_header")); + + return verifyMatryoshkaLevel(body, metaHeader, verifyHeader); + } + + public static boolean verifyMatryoshkaLevel(AbstractMessage body, + frostFS.session.Types.ResponseMetaHeader meta, + frostFS.session.Types.ResponseVerificationHeader verification) { + if (!verifyMessagePart(verification.getMetaSignature(), meta)) return false; + var origin = verification.getOrigin(); + if (!verifyMessagePart(verification.getOriginSignature(), origin)) return false; + if (origin.getSerializedSize() == 0) { + return verifyMessagePart(verification.getBodySignature(), body); + } + return verification.getBodySignature().getSerializedSize() == 0 + && verifyMatryoshkaLevel(body, meta.getOrigin(), origin); + } + + public static boolean verifyMessagePart(Types.Signature sig, AbstractMessage data) { + if (sig.getSerializedSize() == 0 || sig.getKey().isEmpty() || sig.getSign().isEmpty()) return false; + + var publicKey = getPublicKeyByPublic(sig.getKey().toByteArray()); + + var data2Verify = data.getSerializedSize() == 0 ? new byte[]{} : data.toByteArray(); + return verifyData(publicKey, data2Verify, sig.getSign().toByteArray()); + } + + public static boolean verifyData(PublicKey publicKey, byte[] data, byte[] sig) { + try { + Signature signature = Signature.getInstance("NONEwithECDSAinP1363Format"); + signature.initVerify(publicKey); + signature.update(DigestUtils.sha512(data)); + return signature.verify(Arrays.copyOfRange(sig, 1, sig.length)); + } catch (Exception exp) { + throw new RuntimeException(exp); + } + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/ContainerClient.java b/client/src/main/java/info/FrostFS/sdk/services/ContainerClient.java new file mode 100644 index 0000000..3cdb8c2 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/ContainerClient.java @@ -0,0 +1,16 @@ +package info.FrostFS.sdk.services; + +import info.FrostFS.sdk.jdo.Container; +import info.FrostFS.sdk.jdo.ContainerId; + +import java.util.List; + +public interface ContainerClient { + Container getContainerAsync(ContainerId cid); + + List listContainersAsync(); + + ContainerId createContainerAsync(Container container); + + void deleteContainerAsync(ContainerId cid); +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/ObjectClient.java b/client/src/main/java/info/FrostFS/sdk/services/ObjectClient.java new file mode 100644 index 0000000..682d52a --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/ObjectClient.java @@ -0,0 +1,19 @@ +package info.FrostFS.sdk.services; + +import info.FrostFS.sdk.jdo.*; + +import java.io.FileInputStream; + +public interface ObjectClient { + ObjectHeader getObjectHeadAsync(ContainerId containerId, ObjectId objectId); + + ObjectFrostFs getObjectAsync(ContainerId containerId, ObjectId objectId); + + ObjectId putObjectAsync(ObjectHeader header, FileInputStream payload); + + ObjectId putObjectAsync(ObjectHeader header, byte[] payload); + + void deleteObjectAsync(ContainerId containerId, ObjectId objectId); + + Iterable searchObjectsAsync(ContainerId cid, ObjectFilter... filters); +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/ContainerService.java b/client/src/main/java/info/FrostFS/sdk/services/impl/ContainerService.java new file mode 100644 index 0000000..782c218 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/ContainerService.java @@ -0,0 +1,107 @@ +package info.FrostFS.sdk.services.impl; + +import frostFS.container.ContainerServiceGrpc; +import frostFS.container.Service; +import info.FrostFS.sdk.Client; +import info.FrostFS.sdk.Verifier; +import info.FrostFS.sdk.jdo.Container; +import info.FrostFS.sdk.jdo.ContainerId; +import info.FrostFS.sdk.mappers.ContainerIdMapper; +import info.FrostFS.sdk.mappers.ContainerMapper; +import info.FrostFS.sdk.mappers.OwnerIdMapper; +import info.FrostFS.sdk.mappers.VersionMapper; +import info.FrostFS.sdk.services.ContainerClient; + +import java.util.List; +import java.util.stream.Collectors; + +import static info.FrostFS.sdk.RequestConstructor.addMetaHeader; +import static info.FrostFS.sdk.RequestSigner.sign; +import static info.FrostFS.sdk.RequestSigner.signRFC6979; + +public class ContainerService implements ContainerClient { + private final ContainerServiceGrpc.ContainerServiceBlockingStub containerServiceAsyncClient; + private final Client client; + + public ContainerService(ContainerServiceGrpc.ContainerServiceBlockingStub containerServiceAsyncClient, Client client) { + this.containerServiceAsyncClient = containerServiceAsyncClient; + this.client = client; + } + + public Container getContainerAsync(ContainerId cid) { + var request = Service.GetRequest.newBuilder() + .setBody( + Service.GetRequest.Body.newBuilder().setContainerId(ContainerIdMapper.toGrpcMessage(cid)).build() + ); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = containerServiceAsyncClient.get(request.build()); + + Verifier.checkResponse(response); + return ContainerMapper.toModel(response.getBody().getContainer()); + } + + public List listContainersAsync() { + var request = Service.ListRequest.newBuilder() + .setBody( + Service.ListRequest.Body.newBuilder().setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId())).build() + ); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = containerServiceAsyncClient.list(request.build()); + + Verifier.checkResponse(response); + + return response.getBody().getContainerIdsList().stream() + .map(cid -> ContainerId.fromHash(cid.getValue().toByteArray())) + .collect(Collectors.toList()); + } + + public ContainerId createContainerAsync(Container container) { + var grpcContainer = ContainerMapper.toGrpcMessage(container); + + grpcContainer = grpcContainer.toBuilder() + .setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId())) + .setVersion(VersionMapper.toGrpcMessage(client.getVersion())) + .build(); + + var request = Service.PutRequest.newBuilder() + .setBody( + Service.PutRequest.Body.newBuilder() + .setContainer(grpcContainer) + .setSignature(signRFC6979(client.getPublicKey(), client.getPrivateKey(), grpcContainer)) + .build() + ); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = containerServiceAsyncClient.put(request.build()); + + Verifier.checkResponse(response); + return ContainerId.fromHash(response.getBody().getContainerId().getValue().toByteArray()); + } + + public void deleteContainerAsync(ContainerId cid) { + var grpcContainerId = ContainerIdMapper.toGrpcMessage(cid); + + var request = Service.DeleteRequest.newBuilder() + .setBody( + Service.DeleteRequest.Body.newBuilder() + .setContainerId(grpcContainerId) + .setSignature(signRFC6979(client.getPublicKey(), client.getPrivateKey(), grpcContainerId.getValue())) + .build() + ); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = containerServiceAsyncClient.delete(request.build()); + + Verifier.checkResponse(response); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/FrostFSClient.java b/client/src/main/java/info/FrostFS/sdk/services/impl/FrostFSClient.java new file mode 100644 index 0000000..a720d4c --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/FrostFSClient.java @@ -0,0 +1,85 @@ +package info.FrostFS.sdk.services.impl; + +import info.FrostFS.sdk.Client; +import info.FrostFS.sdk.GrpcClient; +import info.FrostFS.sdk.jdo.*; +import info.FrostFS.sdk.services.ContainerClient; +import info.FrostFS.sdk.services.ObjectClient; + +import java.io.FileInputStream; +import java.util.List; + +public class FrostFSClient implements ContainerClient, ObjectClient { + private final ContainerService containerService; + private final NetmapService netmapService; + private final ObjectService objectService; + + public FrostFSClient(GrpcClient grpcClient, Client client) { + this.containerService = new ContainerService(grpcClient.getContainerServiceBlockingClient(), client); + this.netmapService = new NetmapService(grpcClient.getNetmapServiceBlockingClient(), client); + SessionService sessionService = new SessionService(grpcClient.getSessionServiceBlockingClient(), client); + this.objectService = new ObjectService( + grpcClient.getObjectServiceBlockingClient(), grpcClient.getObjectServiceClient(), sessionService, client + ); + checkFrostFsVersionSupport(client); + } + + private void checkFrostFsVersionSupport(Client client) { + var localNodeInfo = netmapService.getLocalNodeInfoAsync(); + if (!localNodeInfo.getVersion().isSupported(client.getVersion())) { + var msg = String.format("FrostFS %s is not supported.", localNodeInfo.getVersion()); + System.out.println(msg); + throw new IllegalArgumentException(msg); + } + } + + @Override + public Container getContainerAsync(ContainerId cid) { + return containerService.getContainerAsync(cid); + } + + @Override + public List listContainersAsync() { + return containerService.listContainersAsync(); + } + + @Override + public ContainerId createContainerAsync(Container container) { + return containerService.createContainerAsync(container); + } + + @Override + public void deleteContainerAsync(ContainerId cid) { + containerService.deleteContainerAsync(cid); + } + + @Override + public ObjectHeader getObjectHeadAsync(ContainerId containerId, ObjectId objectId) { + return objectService.getObjectHeadAsync(containerId, objectId); + } + + @Override + public ObjectFrostFs getObjectAsync(ContainerId containerId, ObjectId objectId) { + return objectService.getObjectAsync(containerId, objectId); + } + + @Override + public ObjectId putObjectAsync(ObjectHeader header, FileInputStream payload) { + return objectService.putObjectAsync(header, payload); + } + + @Override + public ObjectId putObjectAsync(ObjectHeader header, byte[] payload) { + return objectService.putObjectAsync(header, payload); + } + + @Override + public void deleteObjectAsync(ContainerId containerId, ObjectId objectId) { + objectService.deleteObjectAsync(containerId, objectId); + } + + @Override + public Iterable searchObjectsAsync(ContainerId cid, ObjectFilter... filters) { + return objectService.searchObjectsAsync(cid, filters); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/NetmapService.java b/client/src/main/java/info/FrostFS/sdk/services/impl/NetmapService.java new file mode 100644 index 0000000..eafc40f --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/NetmapService.java @@ -0,0 +1,42 @@ +package info.FrostFS.sdk.services.impl; + +import frostFS.netmap.NetmapServiceGrpc; +import frostFS.netmap.Service; +import info.FrostFS.sdk.Client; +import info.FrostFS.sdk.jdo.netmap.NodeInfo; +import info.FrostFS.sdk.mappers.netmap.NodeInfoMapper; + +import static info.FrostFS.sdk.RequestConstructor.addMetaHeader; +import static info.FrostFS.sdk.RequestSigner.sign; + +public class NetmapService { + private final NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceAsyncClient; + private final Client client; + + public NetmapService(NetmapServiceGrpc.NetmapServiceBlockingStub netmapServiceAsyncClient, Client client) { + this.netmapServiceAsyncClient = netmapServiceAsyncClient; + this.client = client; + } + + public NodeInfo getLocalNodeInfoAsync() { + var request = Service.LocalNodeInfoRequest.newBuilder() + .setBody(Service.LocalNodeInfoRequest.Body.newBuilder().build()); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = netmapServiceAsyncClient.localNodeInfo(request.build()); + + return NodeInfoMapper.toModel(response.getBody()); + } + + public Service.NetworkInfoResponse getNetworkInfoAsync() { + var request = Service.NetworkInfoRequest.newBuilder() + .setBody(Service.NetworkInfoRequest.Body.newBuilder().build()); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + return netmapServiceAsyncClient.networkInfo(request.build()); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectReader.java b/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectReader.java new file mode 100644 index 0000000..8350bd7 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectReader.java @@ -0,0 +1,48 @@ +package info.FrostFS.sdk.services.impl; + +import frostFS.object.Service; +import frostFS.object.Types; +import info.FrostFS.sdk.Verifier; + +import java.util.Iterator; + +public class ObjectReader { + public Iterator call; + + public ObjectReader(Iterator call) { + this.call = call; + } + + public Types.Object readHeader() { + if (!call.hasNext()) { + throw new IllegalArgumentException("unexpected end of stream"); + } + + var response = call.next(); + Verifier.checkResponse(response); + + if (response.getBody().getObjectPartCase().getNumber() != Service.GetResponse.Body.INIT_FIELD_NUMBER) { + throw new IllegalArgumentException("unexpected message type"); + } + + return Types.Object.newBuilder() + .setObjectId(response.getBody().getInit().getObjectId()) + .setHeader(response.getBody().getInit().getHeader()) + .build(); + } + + public byte[] readChunk() { + if (!call.hasNext()) { + return null; + } + + var response = call.next(); + Verifier.checkResponse(response); + + if (response.getBody().getObjectPartCase().getNumber() != Service.GetResponse.Body.CHUNK_FIELD_NUMBER) { + throw new IllegalArgumentException("unexpected message type"); + } + + return response.getBody().getChunk().toByteArray(); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectService.java b/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectService.java new file mode 100644 index 0000000..f90fe84 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectService.java @@ -0,0 +1,257 @@ +package info.FrostFS.sdk.services.impl; + +import com.google.common.collect.Iterables; +import com.google.protobuf.ByteString; +import frostFS.object.ObjectServiceGrpc; +import frostFS.object.Service; +import frostFS.refs.Types; +import info.FrostFS.sdk.Client; +import info.FrostFS.sdk.Verifier; +import info.FrostFS.sdk.jdo.*; +import info.FrostFS.sdk.mappers.*; +import info.FrostFS.sdk.services.ObjectClient; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static info.FrostFS.sdk.Helper.getSha256; +import static info.FrostFS.sdk.RequestConstructor.addMetaHeader; +import static info.FrostFS.sdk.RequestConstructor.addObjectSessionToken; +import static info.FrostFS.sdk.RequestSigner.sign; +import static java.util.Objects.nonNull; + +public class ObjectService implements ObjectClient { + private final ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceBlockingClient; + private final ObjectServiceGrpc.ObjectServiceStub objectServiceClient; + private final SessionService sessionService; + private final Client client; + + + public ObjectService(ObjectServiceGrpc.ObjectServiceBlockingStub objectServiceAsyncClient, + ObjectServiceGrpc.ObjectServiceStub objectServiceClient, + SessionService sessionService, + Client client) { + this.objectServiceBlockingClient = objectServiceAsyncClient; + this.objectServiceClient = objectServiceClient; + this.client = client; + this.sessionService = sessionService; + } + + @Override + public ObjectHeader getObjectHeadAsync(ContainerId cid, ObjectId oid) { + var request = Service.HeadRequest.newBuilder() + .setBody( + Service.HeadRequest.Body.newBuilder() + .setAddress( + Types.Address.newBuilder() + .setContainerId(ContainerIdMapper.toGrpcMessage(cid)) + .setObjectId(ObjectIdMapper.toGrpcMessage(oid)) + .build() + ).build() + ); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = objectServiceBlockingClient.head(request.build()); + Verifier.checkResponse(response); + + return ObjectHeaderMapper.toModel(response.getBody().getHeader().getHeader()); + } + + @Override + public ObjectFrostFs getObjectAsync(ContainerId cid, ObjectId oid) { + var sessionToken = sessionService.createSessionAsync(-1); + + var request = Service.GetRequest.newBuilder() + .setBody( + Service.GetRequest.Body.newBuilder() + .setAddress( + Types.Address.newBuilder() + .setContainerId(ContainerIdMapper.toGrpcMessage(cid)) + .setObjectId(ObjectIdMapper.toGrpcMessage(oid)) + .build() + ) + .setRaw(false) + .build() + ); + + addMetaHeader(request, null); + addObjectSessionToken( + request, sessionToken, ContainerIdMapper.toGrpcMessage(cid), ObjectIdMapper.toGrpcMessage(oid), + frostFS.session.Types.ObjectSessionContext.Verb.GET, client.getPublicKey(), client.getPrivateKey() + ); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var obj = getObject(request.build()); + + return ObjectMapper.toModel(obj); + } + + @Override + public void deleteObjectAsync(ContainerId cid, ObjectId oid) { + var request = Service.DeleteRequest.newBuilder() + .setBody( + Service.DeleteRequest.Body.newBuilder() + .setAddress( + Types.Address.newBuilder() + .setContainerId(ContainerIdMapper.toGrpcMessage(cid)) + .setObjectId(ObjectIdMapper.toGrpcMessage(oid)) + .build() + ) + .build()); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var response = objectServiceBlockingClient.delete(request.build()); + Verifier.checkResponse(response); + } + + @Override + public ObjectId putObjectAsync(ObjectHeader header, FileInputStream payload) { + return putObject(header, payload); + } + + @Override + public ObjectId putObjectAsync(ObjectHeader header, byte[] payload) { + return putObject(header, new ByteArrayInputStream(payload)); + } + + @Override + public Iterable searchObjectsAsync(ContainerId cid, ObjectFilter... filters) { + var body = Service.SearchRequest.Body.newBuilder() + .setContainerId(ContainerIdMapper.toGrpcMessage(cid)) + .setVersion(1); + + for (ObjectFilter filter : filters) { + body.addFilters(ObjectFilterMapper.toGrpcMessage(filter)); + } + + var request = Service.SearchRequest.newBuilder() + .setBody(body.build()); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + var objectsIds = searchObjects(request.build()); + + return Iterables.transform(objectsIds, input -> ObjectId.fromHash(input.getValue().toByteArray())); + } + + private frostFS.object.Types.Object getObject(Service.GetRequest request) { + var iterator = getObjectInit(request); + var obj = iterator.readHeader(); + var payload = new byte[Math.toIntExact(obj.getHeader().getPayloadLength())]; + var offset = 0; + var chunk = iterator.readChunk(); + + while (nonNull(chunk)) { + System.arraycopy(chunk, 0, payload, offset, chunk.length); + offset += chunk.length; + chunk = iterator.readChunk(); + } + + return obj.toBuilder().setPayload(ByteString.copyFrom(payload)).build(); + } + + private ObjectReader getObjectInit(Service.GetRequest initRequest) { + if (initRequest.getSerializedSize() == 0) { + throw new IllegalArgumentException(initRequest.getClass().getName()); + } + + return new ObjectReader(objectServiceBlockingClient.get(initRequest)); + } + + private ObjectId putObject(ObjectHeader header, InputStream payload) { + var sessionToken = sessionService.createSessionAsync(-1); + var hdr = ObjectHeaderMapper.toGrpcMessage(header); + + hdr = hdr.toBuilder() + .setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId())) + .setVersion(VersionMapper.toGrpcMessage(client.getVersion())) + .build(); + + var oid = Types.ObjectID.newBuilder().setValue(getSha256(hdr)).build(); + + var request = Service.PutRequest.newBuilder() + .setBody( + Service.PutRequest.Body.newBuilder() + .setInit( + Service.PutRequest.Body.Init.newBuilder().setHeader(hdr).build() + ).build() + ); + + addMetaHeader(request, null); + addObjectSessionToken( + request, sessionToken, hdr.getContainerId(), oid, frostFS.session.Types.ObjectSessionContext.Verb.PUT, + client.getPublicKey(), client.getPrivateKey() + ); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + + var writer = putObjectInit(request.build()); + + var buffer = new byte[Constants.OBJECT_CHUNK_SIZE]; + int bufferLength = 0; + try { + bufferLength = payload.readNBytes(buffer, 0, Constants.OBJECT_CHUNK_SIZE); + while (bufferLength > 0) { + request.setBody( + Service.PutRequest.Body.newBuilder() + .setChunk(ByteString.copyFrom(Arrays.copyOfRange(buffer, 0, bufferLength))) + .build() + ) + .clearVerifyHeader(); + sign(request, client.getPublicKey(), client.getPrivateKey()); + writer.write(request.build()); + bufferLength = payload.readNBytes(buffer, 0, Constants.OBJECT_CHUNK_SIZE); + } + } catch ( + IOException e) { + throw new RuntimeException(e); + } + + var response = writer.complete(); + Verifier.checkResponse(response); + + return ObjectId.fromHash(response.getBody().getObjectId().getValue().toByteArray()); + } + + private ObjectWriter putObjectInit(Service.PutRequest initRequest) { + if (initRequest.getSerializedSize() == 0) { + throw new IllegalArgumentException(initRequest.getClass().getName()); + } + + ObjectWriter writer = new ObjectWriter(objectServiceClient); + writer.write(initRequest); + return writer; + } + + private Iterable searchObjects(Service.SearchRequest request) { + var reader = getSearchReader(request); + var ids = reader.read(); + List 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)); + } + +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectWriter.java b/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectWriter.java new file mode 100644 index 0000000..01ae85e --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/ObjectWriter.java @@ -0,0 +1,59 @@ +package info.FrostFS.sdk.services.impl; + +import frostFS.object.ObjectServiceGrpc; +import frostFS.object.Service; +import io.grpc.stub.StreamObserver; + +import static java.util.Objects.isNull; + +public class ObjectWriter { + private final StreamObserver 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 { + 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; + } + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/SearchReader.java b/client/src/main/java/info/FrostFS/sdk/services/impl/SearchReader.java new file mode 100644 index 0000000..b0d3620 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/SearchReader.java @@ -0,0 +1,26 @@ +package info.FrostFS.sdk.services.impl; + +import frostFS.object.Service; +import info.FrostFS.sdk.Verifier; + +import java.util.Iterator; +import java.util.List; + +public class SearchReader { + public Iterator call; + + public SearchReader(Iterator call) { + this.call = call; + } + + public List read() { + if (!call.hasNext()) { + return null; + } + + var response = call.next(); + Verifier.checkResponse(response); + + return response.getBody().getIdListList(); + } +} diff --git a/client/src/main/java/info/FrostFS/sdk/services/impl/SessionService.java b/client/src/main/java/info/FrostFS/sdk/services/impl/SessionService.java new file mode 100644 index 0000000..832dc52 --- /dev/null +++ b/client/src/main/java/info/FrostFS/sdk/services/impl/SessionService.java @@ -0,0 +1,55 @@ +package info.FrostFS.sdk.services.impl; + +import frostFS.session.Service; +import frostFS.session.SessionServiceGrpc; +import frostFS.session.Types; +import info.FrostFS.sdk.Client; +import info.FrostFS.sdk.mappers.OwnerIdMapper; + +import static info.FrostFS.sdk.RequestConstructor.addMetaHeader; +import static info.FrostFS.sdk.RequestSigner.sign; + +public class SessionService { + private final SessionServiceGrpc.SessionServiceBlockingStub sessionServiceAsyncClient; + private final Client client; + + public SessionService(SessionServiceGrpc.SessionServiceBlockingStub sessionServiceAsyncClient, Client client) { + this.sessionServiceAsyncClient = sessionServiceAsyncClient; + this.client = client; + } + + protected Types.SessionToken createSessionAsync(long expiration) { + var request = Service.CreateRequest.newBuilder() + .setBody( + Service.CreateRequest.Body.newBuilder() + .setOwnerId(OwnerIdMapper.toGrpcMessage(client.getOwnerId())) + .setExpiration(expiration).build() + ); + + addMetaHeader(request, null); + sign(request, client.getPublicKey(), client.getPrivateKey()); + + return createSession(request.build()); + } + + private Types.SessionToken createSession(Service.CreateRequest request) { + var resp = sessionServiceAsyncClient.create(request); + + var lifetime = Types.SessionToken.Body.TokenLifetime.newBuilder() + .setExp(request.getBody().getExpiration()) + .setIat(resp.getMetaHeader().getEpoch()) + .setNbf(resp.getMetaHeader().getEpoch()) + .build(); + + var body = Types.SessionToken.Body.newBuilder() + .setId(resp.getBody().getId()) + .setSessionKey(resp.getBody().getSessionKey()) + .setOwnerId(request.getBody().getOwnerId()) + .setLifetime(lifetime) + .build(); + + return Types.SessionToken.newBuilder() + .setBody(body) + .build(); + } +} diff --git a/cryptography/pom.xml b/cryptography/pom.xml new file mode 100644 index 0000000..9af2880 --- /dev/null +++ b/cryptography/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + info.FrostFS.sdk + FrostFS-sdk-java + 0.1.0 + + + cryptography + + + 11 + 11 + UTF-8 + 3.23.0 + + + + + commons-codec + commons-codec + 1.17.0 + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + org.bouncycastle + bcprov-jdk18on + 1.78.1 + + + org.bouncycastle + bcpkix-jdk18on + 1.78.1 + + + + \ No newline at end of file diff --git a/cryptography/src/main/java/info/FrostFS/sdk/ArrayHelper.java b/cryptography/src/main/java/info/FrostFS/sdk/ArrayHelper.java new file mode 100644 index 0000000..5274f60 --- /dev/null +++ b/cryptography/src/main/java/info/FrostFS/sdk/ArrayHelper.java @@ -0,0 +1,12 @@ +package info.FrostFS.sdk; + +public class ArrayHelper { + + public static byte[] concat(byte[] startArray, byte[] endArray) { + byte[] result = new byte[startArray.length + endArray.length]; + + System.arraycopy(startArray, 0, result, 0, startArray.length); + System.arraycopy(endArray, 0, result, startArray.length, endArray.length); + return result; + } +} diff --git a/cryptography/src/main/java/info/FrostFS/sdk/Base58.java b/cryptography/src/main/java/info/FrostFS/sdk/Base58.java new file mode 100644 index 0000000..9fe27af --- /dev/null +++ b/cryptography/src/main/java/info/FrostFS/sdk/Base58.java @@ -0,0 +1,127 @@ +package info.FrostFS.sdk; + +import java.util.Arrays; + +import static info.FrostFS.sdk.ArrayHelper.concat; +import static info.FrostFS.sdk.Helper.getSha256; +import static java.util.Objects.isNull; + +public class Base58 { + public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final char ENCODED_ZERO = ALPHABET[0]; + private static final int[] INDEXES = new int[128]; + + static { + Arrays.fill(INDEXES, -1); + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + public static byte[] base58CheckDecode(String input) { + if (isNull(input) || input.isEmpty()) { + throw new IllegalArgumentException("Input value is missing"); + } + + byte[] buffer = decode(input); + if (buffer.length < 4) { + throw new IllegalArgumentException(); + } + + byte[] decode = Arrays.copyOfRange(buffer, 0, buffer.length - 4); + byte[] checksum = getSha256(getSha256(decode)); + if (!Arrays.equals( + Arrays.copyOfRange(buffer, buffer.length - 4, buffer.length), + Arrays.copyOfRange(checksum, 0, 4) + )) { + throw new IllegalArgumentException(); + } + + return decode; + } + + public static String base58CheckEncode(byte[] data) { + byte[] checksum = getSha256(getSha256(data)); + var buffer = concat(data, Arrays.copyOfRange(checksum, 0, 4)); + var ret = encode(buffer); + return ret; + } + + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input.length && input[zeros] == 0) { + ++zeros; + } + // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) + input = Arrays.copyOf(input, input.length); // since we modify it in-place + char[] encoded = new char[input.length * 2]; // upper bound + int outputStart = encoded.length; + for (int inputStart = zeros; inputStart < input.length; ) { + encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; + if (input[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. + while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart; + } + while (--zeros >= 0) { + encoded[--outputStart] = ENCODED_ZERO; + } + // Return encoded string (including encoded leading zeros). + return new String(encoded, outputStart, encoded.length - outputStart); + } + + public static byte[] decode(String input) { + if (input.isEmpty()) { + return new byte[0]; + } + // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). + byte[] input58 = new byte[input.length()]; + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + int digit = c < 128 ? INDEXES[c] : -1; + if (digit < 0) { + throw new IllegalArgumentException(String.format("Invalid character in Base58: 0x%04x", (int) c)); + } + input58[i] = (byte) digit; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input58.length && input58[zeros] == 0) { + ++zeros; + } + // Convert base-58 digits to base-256 digits. + byte[] decoded = new byte[input.length()]; + int outputStart = decoded.length; + for (int inputStart = zeros; inputStart < input58.length; ) { + decoded[--outputStart] = divmod(input58, inputStart, 58, 256); + if (input58[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Ignore extra leading zeroes that were added during the calculation. + while (outputStart < decoded.length && decoded[outputStart] == 0) { + ++outputStart; + } + // Return decoded data (including original number of leading zeros). + return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); + } + + private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { + // this is just long division which accounts for the base of the input digits + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = (int) number[i] & 0xFF; + int temp = remainder * base + digit; + number[i] = (byte) (temp / divisor); + remainder = temp % divisor; + } + return (byte) remainder; + } +} diff --git a/cryptography/src/main/java/info/FrostFS/sdk/Helper.java b/cryptography/src/main/java/info/FrostFS/sdk/Helper.java new file mode 100644 index 0000000..e4e1460 --- /dev/null +++ b/cryptography/src/main/java/info/FrostFS/sdk/Helper.java @@ -0,0 +1,36 @@ +package info.FrostFS.sdk; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ByteString; +import org.bouncycastle.crypto.digests.RIPEMD160Digest; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Helper { + + public static byte[] getRIPEMD160(byte[] value) { + var hash = new byte[20]; + var digest = new RIPEMD160Digest(); + digest.update(value, 0, value.length); + digest.doFinal(hash, 0); + return hash; + } + + public static byte[] getSha256(byte[] value) { + try { + return MessageDigest.getInstance("SHA-256").digest(value); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static ByteString getSha256(AbstractMessage value) { + return ByteString.copyFrom(getSha256(value.toByteArray())); + } + + public static String пуеHexString(byte[] array) { + return String.format("%0" + (array.length << 1) + "x", new BigInteger(1, array)); + } +} diff --git a/cryptography/src/main/java/info/FrostFS/sdk/KeyExtension.java b/cryptography/src/main/java/info/FrostFS/sdk/KeyExtension.java new file mode 100644 index 0000000..b8c5ab6 --- /dev/null +++ b/cryptography/src/main/java/info/FrostFS/sdk/KeyExtension.java @@ -0,0 +1,161 @@ +package info.FrostFS.sdk; + +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static info.FrostFS.sdk.Helper.getRIPEMD160; +import static info.FrostFS.sdk.Helper.getSha256; +import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray; + +public class KeyExtension { + public static final byte NEO_ADDRESS_VERSION = 0x35; + private static final int COMPRESSED_PUBLIC_KEY_LENGTH = 33; + private static final int UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65; + + private static final int CHECK_SIG_DESCRIPTOR = ByteBuffer + .wrap(getSha256("System.Crypto.CheckSig".getBytes(StandardCharsets.US_ASCII))) + .order(ByteOrder.LITTLE_ENDIAN).getInt(); + + + public static byte[] compress(byte[] publicKey) { + if (publicKey.length != UNCOMPRESSED_PUBLIC_KEY_LENGTH) { + throw new IllegalArgumentException( + String.format("Compress argument isn't uncompressed public key. Expected length=%s, actual=%s", + UNCOMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length) + ); + } + + var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); + var point = secp256R1.getCurve().decodePoint(publicKey); + return point.getEncoded(true); + } + + public static byte[] getPrivateKeyFromWIF(String wif) { + var data = Base58.base58CheckDecode(wif); + return Arrays.copyOfRange(data, 1, data.length - 1); + } + + public static byte[] loadPublicKey(byte[] privateKey) { + X9ECParameters params = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); + ECDomainParameters domain = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + ECPoint q = domain.getG().multiply(new BigInteger(1, privateKey)); + ECPublicKeyParameters publicParams = new ECPublicKeyParameters(q, domain); + return publicParams.getQ().getEncoded(true); + } + + public static PrivateKey loadPrivateKey(byte[] privateKey) { + X9ECParameters params = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); + ECDomainParameters domain = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + ECPrivateKeyParameters ecParams = new ECPrivateKeyParameters(fromUnsignedByteArray(privateKey), domain); + + ECParameterSpec ecParameterSpec = new ECNamedCurveSpec( + "secp256r1", params.getCurve(), params.getG(), params.getN(), params.getH() + ); + ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(ecParams.getD(), ecParameterSpec); + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + return kf.generatePrivate(privateKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } + + public static PublicKey getPublicKeyByPublic(byte[] publicKey) { + if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { + throw new IllegalArgumentException( + String.format("Decompress argument isn't compressed public key. Expected length=%s, actual=%s", + COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length) + ); + } + + X9ECParameters secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); + ECDomainParameters domain = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH()); + var ecPoint = secp256R1.getCurve().decodePoint(publicKey); + var publicParams = new ECPublicKeyParameters(ecPoint, domain); + java.security.spec.ECPoint point = new java.security.spec.ECPoint( + publicParams.getQ().getRawXCoord().toBigInteger(), publicParams.getQ().getRawYCoord().toBigInteger() + ); + ECParameterSpec ecParameterSpec = new ECNamedCurveSpec( + "secp256r1", secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN(), secp256R1.getH(), secp256R1.getSeed() + ); + ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecParameterSpec); + + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + return kf.generatePublic(publicKeySpec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + public static byte[] getScriptHash(byte[] publicKey) { + var script = createSignatureRedeemScript(publicKey); + + return getRIPEMD160(getSha256(script)); + } + + public static String publicKeyToAddress(byte[] publicKey) { + if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { + throw new IllegalArgumentException( + String.format("PublicKey isn't encoded compressed public key. Expected length=%s, actual=%s", + COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length) + ); + } + + return toAddress(getScriptHash(publicKey), NEO_ADDRESS_VERSION); + } + + private static String toAddress(byte[] scriptHash, byte version) { + byte[] data = new byte[21]; + data[0] = version; + System.arraycopy(scriptHash, 0, data, 1, scriptHash.length); + return Base58.base58CheckEncode(data); + } + + private static byte[] getBytes(int value) { + byte[] buffer = new byte[4]; + + for (int i = 0; i < buffer.length; i++) { + buffer[i] = (byte) (value >> i * 8); + } + + return buffer; + } + + private static byte[] createSignatureRedeemScript(byte[] publicKey) { + if (publicKey.length != COMPRESSED_PUBLIC_KEY_LENGTH) { + throw new IllegalArgumentException( + String.format("PublicKey isn't encoded compressed public key. Expected length=%s, actual=%s", + COMPRESSED_PUBLIC_KEY_LENGTH, publicKey.length) + ); + } + + var script = new byte[]{0x0c, COMPRESSED_PUBLIC_KEY_LENGTH}; //PUSHDATA1 33 + + script = ArrayHelper.concat(script, publicKey); + script = ArrayHelper.concat(script, new byte[]{0x41}); //SYSCALL + script = ArrayHelper.concat(script, getBytes(CHECK_SIG_DESCRIPTOR)); //Neo_Crypto_CheckSig + return script; + } +} diff --git a/modelsV2/pom.xml b/modelsV2/pom.xml new file mode 100644 index 0000000..9093e71 --- /dev/null +++ b/modelsV2/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + info.FrostFS.sdk + FrostFS-sdk-java + 0.1.0 + + + modelsV2 + + + 11 + 11 + UTF-8 + + + + + org.apache.commons + commons-lang3 + 3.14.0 + + + info.FrostFS.sdk + cryptography + 0.1.0 + + + info.FrostFS.sdk + protosV2 + 0.1.0 + + + + \ No newline at end of file diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/UUIDExtension.java b/modelsV2/src/main/java/info/FrostFS/sdk/UUIDExtension.java new file mode 100644 index 0000000..df146ec --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/UUIDExtension.java @@ -0,0 +1,21 @@ +package info.FrostFS.sdk; + +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UUIDExtension { + + public static UUID asUuid(byte[] bytes) { + ByteBuffer bb = ByteBuffer.wrap(bytes); + long firstLong = bb.getLong(); + long secondLong = bb.getLong(); + return new UUID(firstLong, secondLong); + } + + public static byte[] asBytes(UUID id) { + ByteBuffer bb = ByteBuffer.allocate(16); + bb.putLong(id.getMostSignificantBits()); + bb.putLong(id.getLeastSignificantBits()); + return bb.array(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/constants/XHeaderConst.java b/modelsV2/src/main/java/info/FrostFS/sdk/constants/XHeaderConst.java new file mode 100644 index 0000000..8c435e7 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/constants/XHeaderConst.java @@ -0,0 +1,7 @@ +package info.FrostFS.sdk.constants; + +public class XHeaderConst { + public static final String RESERVED_XHEADER_PREFIX = "__SYSTEM__"; + public static final String XHEADER_NETMAP_EPOCH = RESERVED_XHEADER_PREFIX + "NETMAP_EPOCH"; + public static final String XHEADER_NETMAP_LOOKUP_DEPTH = RESERVED_XHEADER_PREFIX + "NETMAP_LOOKUP_DEPTH"; +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/enums/BasicAcl.java b/modelsV2/src/main/java/info/FrostFS/sdk/enums/BasicAcl.java new file mode 100644 index 0000000..b454a68 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/enums/BasicAcl.java @@ -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 ENUM_MAP_BY_VALUE; + + static { + Map 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); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/enums/NodeState.java b/modelsV2/src/main/java/info/FrostFS/sdk/enums/NodeState.java new file mode 100644 index 0000000..e6f1464 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/enums/NodeState.java @@ -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 ENUM_MAP_BY_VALUE; + + static { + Map 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); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/enums/ObjectMatchType.java b/modelsV2/src/main/java/info/FrostFS/sdk/enums/ObjectMatchType.java new file mode 100644 index 0000000..385756f --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/enums/ObjectMatchType.java @@ -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; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/enums/ObjectType.java b/modelsV2/src/main/java/info/FrostFS/sdk/enums/ObjectType.java new file mode 100644 index 0000000..feb545f --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/enums/ObjectType.java @@ -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 ENUM_MAP_BY_VALUE; + + static { + Map 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); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/enums/StatusCode.java b/modelsV2/src/main/java/info/FrostFS/sdk/enums/StatusCode.java new file mode 100644 index 0000000..4e44421 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/enums/StatusCode.java @@ -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 ENUM_MAP_BY_VALUE; + + static { + Map 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); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Constants.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Constants.java new file mode 100644 index 0000000..2c01d9e --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Constants.java @@ -0,0 +1,6 @@ +package info.FrostFS.sdk.jdo; + +public class Constants { + public static final int OBJECT_CHUNK_SIZE = 3 * (1 << 20); + public static final int SHA256_HASH_LENGTH = 32; +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Container.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Container.java new file mode 100644 index 0000000..38df197 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Container.java @@ -0,0 +1,58 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.enums.BasicAcl; +import info.FrostFS.sdk.jdo.netmap.PlacementPolicy; + +import java.util.UUID; + +public class Container { + public UUID nonce; + public BasicAcl basicAcl; + public PlacementPolicy placementPolicy; + public Version version; + + public UUID getNonce() { + return nonce; + } + + public void setNonce(UUID nonce) { + this.nonce = nonce; + } + + public BasicAcl getBasicAcl() { + return basicAcl; + } + + public void setBasicAcl(BasicAcl basicAcl) { + this.basicAcl = basicAcl; + } + + public PlacementPolicy getPlacementPolicy() { + return placementPolicy; + } + + public void setPlacementPolicy(PlacementPolicy placementPolicy) { + this.placementPolicy = placementPolicy; + } + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } + + public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) { + nonce = UUID.randomUUID(); + this.basicAcl = basicAcl; + this.placementPolicy = placementPolicy; + } + + public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy, UUID nonce, Version version) { + this.nonce = nonce; + this.basicAcl = basicAcl; + this.placementPolicy = placementPolicy; + this.version = version; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ContainerId.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ContainerId.java new file mode 100644 index 0000000..309f25b --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ContainerId.java @@ -0,0 +1,35 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.Base58; + +public class ContainerId { + public String value; + + public ContainerId(String value) { + this.value = value; + } + + public static ContainerId fromHash(byte[] hash) { + if (hash.length != Constants.SHA256_HASH_LENGTH) { + throw new IllegalArgumentException("ContainerID must be a sha256 hash."); + } + return new ContainerId(Base58.encode(hash)); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + public byte[] toHash() { + return Base58.decode(value); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/MetaHeader.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/MetaHeader.java new file mode 100644 index 0000000..5f428b4 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/MetaHeader.java @@ -0,0 +1,41 @@ +package info.FrostFS.sdk.jdo; + +public class MetaHeader { + public Version version; + public int epoch; + public int ttl; + + public MetaHeader(Version version, int epoch, int ttl) { + this.version = version; + this.epoch = epoch; + this.ttl = ttl; + } + + public static MetaHeader getDefault() { + return new MetaHeader(new Version(2, 13), 0, 2); + } + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } + + public int getEpoch() { + return epoch; + } + + public void setEpoch(int epoch) { + this.epoch = epoch; + } + + public int getTtl() { + return ttl; + } + + public void setTtl(int ttl) { + this.ttl = ttl; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectAttribute.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectAttribute.java new file mode 100644 index 0000000..1a122f4 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectAttribute.java @@ -0,0 +1,27 @@ +package info.FrostFS.sdk.jdo; + +public class ObjectAttribute { + public String key; + public String value; + + public ObjectAttribute(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectFilter.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectFilter.java new file mode 100644 index 0000000..0fb1db3 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectFilter.java @@ -0,0 +1,58 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.enums.ObjectMatchType; + +public class ObjectFilter { + private static final String HEADER_PREFIX = "Object:"; + + public ObjectMatchType matchType; + public String key; + public String value; + + + public ObjectFilter(ObjectMatchType matchType, String key, String value) { + this.matchType = matchType; + this.key = key; + this.value = value; + } + + public static ObjectFilter ObjectIdFilter(ObjectMatchType matchType, ObjectId objectId) { + return new ObjectFilter(matchType, HEADER_PREFIX + "objectID", objectId.value); + } + + public static ObjectFilter OwnerFilter(ObjectMatchType matchType, OwnerId ownerId) { + return new ObjectFilter(matchType, HEADER_PREFIX + "ownerID", ownerId.getValue()); + } + + public static ObjectFilter RootFilter() { + return new ObjectFilter(ObjectMatchType.UNSPECIFIED, HEADER_PREFIX + "ROOT", ""); + } + + public static ObjectFilter VersionFilter(ObjectMatchType matchType, Version version) { + return new ObjectFilter(matchType, HEADER_PREFIX + "version", version.toString()); + } + + public ObjectMatchType getMatchType() { + return matchType; + } + + public void setMatchType(ObjectMatchType matchType) { + this.matchType = matchType; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectFrostFs.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectFrostFs.java new file mode 100644 index 0000000..c7cc689 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectFrostFs.java @@ -0,0 +1,37 @@ +package info.FrostFS.sdk.jdo; + +public class ObjectFrostFs { + public ObjectHeader header; + public ObjectId objectId; + public byte[] payload; + + public ObjectFrostFs(ObjectHeader header, ObjectId objectId, byte[] payload) { + this.header = header; + this.objectId = objectId; + this.payload = payload; + } + + public ObjectHeader getHeader() { + return header; + } + + public void setHeader(ObjectHeader header) { + this.header = header; + } + + public ObjectId getObjectId() { + return objectId; + } + + public void setObjectId(ObjectId objectId) { + this.objectId = objectId; + } + + public byte[] getPayload() { + return payload; + } + + public void setPayload(byte[] payload) { + this.payload = payload; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectHeader.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectHeader.java new file mode 100644 index 0000000..7dc795c --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectHeader.java @@ -0,0 +1,71 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.enums.ObjectType; + +public class ObjectHeader { + public ObjectAttribute[] attributes; + public ContainerId containerId; + public long size; + public ObjectType objectType; + public Version version; + + public ObjectHeader(ContainerId containerId, ObjectType objectType, ObjectAttribute[] attributes, long size, Version version) { + this.attributes = attributes; + this.containerId = containerId; + this.size = size; + this.objectType = objectType; + this.version = version; + } + + public ObjectHeader(ContainerId containerId, ObjectType type, ObjectAttribute[] attributes) { + this.attributes = attributes; + this.containerId = containerId; + this.objectType = type; + } + + public ObjectHeader(ContainerId containerId, ObjectAttribute[] attributes) { + this.attributes = attributes; + this.containerId = containerId; + this.objectType = ObjectType.REGULAR; + } + + public ObjectAttribute[] getAttributes() { + return attributes; + } + + public void setAttributes(ObjectAttribute[] attributes) { + this.attributes = attributes; + } + + public ContainerId getContainerId() { + return containerId; + } + + public void setContainerId(ContainerId containerId) { + this.containerId = containerId; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public ObjectType getObjectType() { + return objectType; + } + + public void setObjectType(ObjectType objectType) { + this.objectType = objectType; + } + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectId.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectId.java new file mode 100644 index 0000000..438d027 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/ObjectId.java @@ -0,0 +1,31 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.Base58; + +public class ObjectId { + public String value; + + public ObjectId(String id) { + this.value = id; + } + + public static ObjectId fromHash(byte[] hash) { + if (hash.length != Constants.SHA256_HASH_LENGTH) { + throw new IllegalArgumentException("ObjectId must be a sha256 hash."); + } + return new ObjectId(Base58.encode(hash)); + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } + + public byte[] toHash() { + return Base58.decode(value); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/OwnerId.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/OwnerId.java new file mode 100644 index 0000000..723e234 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/OwnerId.java @@ -0,0 +1,25 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.Base58; + +import static info.FrostFS.sdk.KeyExtension.publicKeyToAddress; + +public class OwnerId { + private final String value; + + public OwnerId(String value) { + this.value = value; + } + + public static OwnerId fromKey(byte[] publicKey) { + return new OwnerId(publicKeyToAddress(publicKey)); + } + + public String getValue() { + return value; + } + + public byte[] toHash() { + return Base58.decode(value); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Status.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Status.java new file mode 100644 index 0000000..bcb6f08 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Status.java @@ -0,0 +1,45 @@ +package info.FrostFS.sdk.jdo; + +import info.FrostFS.sdk.enums.StatusCode; + +import java.util.Optional; + +public class Status { + public StatusCode code; + public String message; + + public Status(StatusCode code, String message) { + this.code = code; + this.message = Optional.ofNullable(message).orElse(""); + } + + public Status(StatusCode code) { + this.code = code; + this.message = ""; + } + + public StatusCode getCode() { + return code; + } + + public void setCode(StatusCode code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return "Response status: " + code + ". Message: " + message + "."; + } + + public boolean isSuccess() { + return StatusCode.SUCCESS.equals(code); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Version.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Version.java new file mode 100644 index 0000000..22cd8c7 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/Version.java @@ -0,0 +1,36 @@ +package info.FrostFS.sdk.jdo; + +public class Version { + public int major; + public int minor; + + public int getMajor() { + return major; + } + + public void setMajor(int major) { + this.major = major; + } + + public int getMinor() { + return minor; + } + + public void setMinor(int minor) { + this.minor = minor; + } + + @Override + public String toString() { + return "v" + major + "." + minor; + } + + public Version(int major, int minor) { + this.major = major; + this.minor = minor; + } + + public boolean isSupported(Version version) { + return major == version.getMajor(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/NodeInfo.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/NodeInfo.java new file mode 100644 index 0000000..fe65459 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/NodeInfo.java @@ -0,0 +1,30 @@ +package info.FrostFS.sdk.jdo.netmap; + +import info.FrostFS.sdk.jdo.Version; +import info.FrostFS.sdk.enums.NodeState; + +public class NodeInfo { + public NodeState state; + public Version version; + + public NodeState getState() { + return state; + } + + public void setState(NodeState state) { + this.state = state; + } + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } + + public NodeInfo(NodeState state, Version version) { + this.state = state; + this.version = version; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/PlacementPolicy.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/PlacementPolicy.java new file mode 100644 index 0000000..d32b585 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/PlacementPolicy.java @@ -0,0 +1,27 @@ +package info.FrostFS.sdk.jdo.netmap; + +public class PlacementPolicy { + public Replica[] replicas; + public boolean unique; + + public PlacementPolicy(boolean unique, Replica[] replicas) { + this.replicas = replicas; + this.unique = unique; + } + + public Replica[] getReplicas() { + return replicas; + } + + public void setReplicas(Replica[] replicas) { + this.replicas = replicas; + } + + public boolean isUnique() { + return unique; + } + + public void setUnique(boolean unique) { + this.unique = unique; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/Replica.java b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/Replica.java new file mode 100644 index 0000000..80d1e89 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/jdo/netmap/Replica.java @@ -0,0 +1,34 @@ +package info.FrostFS.sdk.jdo.netmap; + +import java.util.Optional; + +public class Replica { + public int count; + public String selector; + + public Replica(int count, String selector) { + this.count = count; + this.selector = Optional.ofNullable(selector).orElse(""); + } + + public Replica(int count) { + this.count = count; + this.selector = ""; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public String getSelector() { + return selector; + } + + public void setSelector(String selector) { + this.selector = selector; + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ContainerIdMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ContainerIdMapper.java new file mode 100644 index 0000000..bab5b61 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ContainerIdMapper.java @@ -0,0 +1,16 @@ +package info.FrostFS.sdk.mappers; + +import com.google.protobuf.ByteString; +import frostFS.refs.Types; +import info.FrostFS.sdk.jdo.ContainerId; + +public class ContainerIdMapper { + + public static Types.ContainerID toGrpcMessage(ContainerId containerId) { + var test = containerId.toHash(); + + return Types.ContainerID.newBuilder() + .setValue(ByteString.copyFrom(containerId.toHash())) + .build(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ContainerMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ContainerMapper.java new file mode 100644 index 0000000..4f1d886 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ContainerMapper.java @@ -0,0 +1,39 @@ +package info.FrostFS.sdk.mappers; + + +import com.google.protobuf.ByteString; +import frostFS.container.Types; +import info.FrostFS.sdk.enums.BasicAcl; +import info.FrostFS.sdk.jdo.Container; +import info.FrostFS.sdk.mappers.netmap.PlacementPolicyMapper; + +import static info.FrostFS.sdk.UUIDExtension.asBytes; +import static info.FrostFS.sdk.UUIDExtension.asUuid; +import static java.util.Objects.isNull; + +public class ContainerMapper { + + public static Types.Container toGrpcMessage(Container container) { + return Types.Container.newBuilder() + .setBasicAcl(container.getBasicAcl().value) + .setPlacementPolicy(PlacementPolicyMapper.toGrpcMessage(container.getPlacementPolicy())) + .setNonce(ByteString.copyFrom(asBytes(container.getNonce()))) + .build(); + } + + public static Container toModel(Types.Container container) { + var basicAcl = BasicAcl.get(container.getBasicAcl()); + if (isNull(basicAcl)) { + throw new IllegalArgumentException( + String.format("Unknown BasicACL rule. Value: %s.", container.getBasicAcl()) + ); + } + + return new Container( + basicAcl, + PlacementPolicyMapper.toModel(container.getPlacementPolicy()), + asUuid(container.getNonce().toByteArray()), + VersionMapper.toModel(container.getVersion()) + ); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/MetaHeaderMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/MetaHeaderMapper.java new file mode 100644 index 0000000..2d493c4 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/MetaHeaderMapper.java @@ -0,0 +1,15 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.session.Types; +import info.FrostFS.sdk.jdo.MetaHeader; + +public class MetaHeaderMapper { + + public static Types.RequestMetaHeader toGrpcMessage(MetaHeader metaHeader) { + return Types.RequestMetaHeader.newBuilder() + .setVersion(VersionMapper.toGrpcMessage(metaHeader.getVersion())) + .setEpoch(metaHeader.getEpoch()) + .setTtl(metaHeader.getTtl()) + .build(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectAttributeMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectAttributeMapper.java new file mode 100644 index 0000000..45489a5 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectAttributeMapper.java @@ -0,0 +1,17 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.object.Types; +import info.FrostFS.sdk.jdo.ObjectAttribute; + +public class ObjectAttributeMapper { + public static Types.Header.Attribute toGrpcMessage(ObjectAttribute attribute) { + return Types.Header.Attribute.newBuilder() + .setKey(attribute.getKey()) + .setValue(attribute.getValue()) + .build(); + } + + public static ObjectAttribute toModel(Types.Header.Attribute attribute) { + return new ObjectAttribute(attribute.getKey(), attribute.getValue()); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectFilterMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectFilterMapper.java new file mode 100644 index 0000000..328de81 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectFilterMapper.java @@ -0,0 +1,25 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.object.Service; +import frostFS.object.Types; +import info.FrostFS.sdk.jdo.ObjectFilter; +import org.apache.commons.lang3.EnumUtils; + +import static java.util.Objects.isNull; + +public class ObjectFilterMapper { + public static Service.SearchRequest.Body.Filter toGrpcMessage(ObjectFilter filter) { + var objectMatchType = Types.MatchType.forNumber(filter.getMatchType().value); + if (isNull(objectMatchType)) { + throw new IllegalArgumentException( + String.format("Unknown MatchType. Value: %s.", filter.getMatchType().name()) + ); + } + + return Service.SearchRequest.Body.Filter.newBuilder() + .setMatchType(objectMatchType) + .setKey(filter.getKey()) + .setValue(filter.getValue()) + .build(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectHeaderMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectHeaderMapper.java new file mode 100644 index 0000000..ba38b14 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectHeaderMapper.java @@ -0,0 +1,48 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.object.Types; +import info.FrostFS.sdk.enums.ObjectType; +import info.FrostFS.sdk.jdo.ContainerId; +import info.FrostFS.sdk.jdo.ObjectAttribute; +import info.FrostFS.sdk.jdo.ObjectHeader; +import org.apache.commons.lang3.EnumUtils; + +import static java.util.Objects.isNull; + +public class ObjectHeaderMapper { + public static Types.Header toGrpcMessage(ObjectHeader header) { + var objectType = EnumUtils.getEnum(Types.ObjectType.class, header.getObjectType().name()); + if (isNull(objectType)) { + throw new IllegalArgumentException( + String.format("Unknown ObjectType. Value: %s.", header.getObjectType().name()) + ); + } + + var head = Types.Header.newBuilder() + .setContainerId(ContainerIdMapper.toGrpcMessage(header.getContainerId())) + .setObjectType(objectType); + + for (ObjectAttribute objectAttribute : header.getAttributes()) { + head.addAttributes(ObjectAttributeMapper.toGrpcMessage(objectAttribute)); + } + + return head.build(); + } + + public static ObjectHeader toModel(Types.Header header) { + var objectType = ObjectType.get(header.getObjectTypeValue()); + if (isNull(objectType)) { + throw new IllegalArgumentException( + String.format("Unknown ObjectType. Value: %s.", header.getObjectType()) + ); + } + + return new ObjectHeader( + ContainerId.fromHash(header.getContainerId().getValue().toByteArray()), + objectType, + header.getAttributesList().stream().map(ObjectAttributeMapper::toModel).toArray(ObjectAttribute[]::new), + header.getPayloadLength(), + VersionMapper.toModel(header.getVersion()) + ); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectIdMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectIdMapper.java new file mode 100644 index 0000000..42200e2 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectIdMapper.java @@ -0,0 +1,13 @@ +package info.FrostFS.sdk.mappers; + +import com.google.protobuf.ByteString; +import frostFS.refs.Types; +import info.FrostFS.sdk.jdo.ObjectId; + +public class ObjectIdMapper { + public static Types.ObjectID toGrpcMessage(ObjectId objectId) { + return Types.ObjectID.newBuilder() + .setValue(ByteString.copyFrom(objectId.toHash())) + .build(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectMapper.java new file mode 100644 index 0000000..b970456 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/ObjectMapper.java @@ -0,0 +1,15 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.object.Types; +import info.FrostFS.sdk.jdo.ObjectFrostFs; +import info.FrostFS.sdk.jdo.ObjectId; + +public class ObjectMapper { + public static ObjectFrostFs toModel(Types.Object obj) { + return new ObjectFrostFs( + ObjectHeaderMapper.toModel(obj.getHeader()), + ObjectId.fromHash(obj.getObjectId().getValue().toByteArray()), + obj.getPayload().toByteArray() + ); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/OwnerIdMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/OwnerIdMapper.java new file mode 100644 index 0000000..bc7027c --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/OwnerIdMapper.java @@ -0,0 +1,13 @@ +package info.FrostFS.sdk.mappers; + +import com.google.protobuf.ByteString; +import frostFS.refs.Types; +import info.FrostFS.sdk.jdo.OwnerId; + +public class OwnerIdMapper { + public static Types.OwnerID toGrpcMessage(OwnerId ownerId) { + return Types.OwnerID.newBuilder() + .setValue(ByteString.copyFrom(ownerId.toHash())) + .build(); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/StatusMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/StatusMapper.java new file mode 100644 index 0000000..6cb68d7 --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/StatusMapper.java @@ -0,0 +1,23 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.status.Types; +import info.FrostFS.sdk.enums.StatusCode; +import info.FrostFS.sdk.jdo.Status; + +import static java.util.Objects.isNull; + +public class StatusMapper { + public static Status toModel(Types.Status status) { + if (isNull(status)) return new Status(StatusCode.SUCCESS); + + var statusCode = StatusCode.get(status.getCode()); + if (isNull(statusCode)) { + throw new IllegalArgumentException( + String.format("Unknown StatusCode. Value: %s.", status.getCode()) + ); + + } + + return new Status(statusCode, status.getMessage()); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/VersionMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/VersionMapper.java new file mode 100644 index 0000000..339746a --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/VersionMapper.java @@ -0,0 +1,18 @@ +package info.FrostFS.sdk.mappers; + +import frostFS.refs.Types; +import info.FrostFS.sdk.jdo.Version; + +public class VersionMapper { + + public static Types.Version toGrpcMessage(Version version) { + return Types.Version.newBuilder() + .setMajor(version.getMajor()) + .setMinor(version.getMinor()) + .build(); + } + + public static Version toModel(Types.Version version) { + return new Version(version.getMajor(), version.getMinor()); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/NodeInfoMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/NodeInfoMapper.java new file mode 100644 index 0000000..934d60f --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/NodeInfoMapper.java @@ -0,0 +1,21 @@ +package info.FrostFS.sdk.mappers.netmap; + +import frostFS.netmap.Service; +import info.FrostFS.sdk.enums.NodeState; +import info.FrostFS.sdk.jdo.netmap.NodeInfo; +import info.FrostFS.sdk.mappers.VersionMapper; + +import static java.util.Objects.isNull; + +public class NodeInfoMapper { + public static NodeInfo toModel(Service.LocalNodeInfoResponse.Body nodeInfo) { + var nodeState = NodeState.get(nodeInfo.getNodeInfo().getState().getNumber()); + if (isNull(nodeState)) { + throw new IllegalArgumentException( + String.format("Unknown NodeState. Value: %s.", nodeInfo.getNodeInfo().getState()) + ); + } + + return new NodeInfo(nodeState, VersionMapper.toModel(nodeInfo.getVersion())); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/PlacementPolicyMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/PlacementPolicyMapper.java new file mode 100644 index 0000000..871dfcb --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/PlacementPolicyMapper.java @@ -0,0 +1,25 @@ +package info.FrostFS.sdk.mappers.netmap; + +import frostFS.netmap.Types; +import info.FrostFS.sdk.jdo.netmap.PlacementPolicy; +import info.FrostFS.sdk.jdo.netmap.Replica; + +public class PlacementPolicyMapper { + public static Types.PlacementPolicy toGrpcMessage(PlacementPolicy placementPolicy) { + var pp = Types.PlacementPolicy.newBuilder() + .setUnique(placementPolicy.isUnique()); + + for (Replica replica : placementPolicy.getReplicas()) { + pp.addReplicas(ReplicaMapper.toGrpcMessage(replica)); + } + + return pp.build(); + } + + public static PlacementPolicy toModel(Types.PlacementPolicy placementPolicy) { + return new PlacementPolicy( + placementPolicy.getUnique(), + placementPolicy.getReplicasList().stream().map(ReplicaMapper::toModel).toArray(Replica[]::new) + ); + } +} diff --git a/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/ReplicaMapper.java b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/ReplicaMapper.java new file mode 100644 index 0000000..93735ff --- /dev/null +++ b/modelsV2/src/main/java/info/FrostFS/sdk/mappers/netmap/ReplicaMapper.java @@ -0,0 +1,17 @@ +package info.FrostFS.sdk.mappers.netmap; + +import frostFS.netmap.Types; +import info.FrostFS.sdk.jdo.netmap.Replica; + +public class ReplicaMapper { + public static Types.Replica toGrpcMessage(Replica replica) { + return Types.Replica.newBuilder() + .setCount(replica.getCount()) + .setSelector(replica.getSelector()) + .build(); + } + + public static Replica toModel(Types.Replica replica) { + return new Replica(replica.getCount(), replica.getSelector()); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5c16e71 --- /dev/null +++ b/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + info.FrostFS.sdk + FrostFS-sdk-java + 0.1.0 + pom + + protosV2 + client + cryptography + modelsV2 + + + + 11 + 11 + UTF-8 + + \ No newline at end of file diff --git a/protosV2/pom.xml b/protosV2/pom.xml new file mode 100644 index 0000000..d7d8630 --- /dev/null +++ b/protosV2/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + info.FrostFS.sdk + FrostFS-sdk-java + 0.1.0 + + + protosV2 + + + 11 + 11 + 3.23.0 + 1.62.2 + UTF-8 + + + + + io.grpc + grpc-netty + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-services + ${grpc.version} + runtime + + + javax.annotation + javax.annotation-api + 1.3.2 + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + + + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + + + + + + + kr.motd.maven + os-maven-plugin + 1.6.2 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + compile + compile-custom + + test-compile + test-compile-custom + + + + + + + + \ No newline at end of file diff --git a/protosV2/src/main/proto/accounting/service.proto b/protosV2/src/main/proto/accounting/service.proto new file mode 100644 index 0000000..c225c58 --- /dev/null +++ b/protosV2/src/main/proto/accounting/service.proto @@ -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; +} diff --git a/protosV2/src/main/proto/accounting/types.proto b/protosV2/src/main/proto/accounting/types.proto new file mode 100644 index 0000000..57229d9 --- /dev/null +++ b/protosV2/src/main/proto/accounting/types.proto @@ -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" ]; +} diff --git a/protosV2/src/main/proto/acl/types.proto b/protosV2/src/main/proto/acl/types.proto new file mode 100644 index 0000000..86a9311 --- /dev/null +++ b/protosV2/src/main/proto/acl/types.proto @@ -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" ]; +} diff --git a/protosV2/src/main/proto/apemanager/service.proto b/protosV2/src/main/proto/apemanager/service.proto new file mode 100644 index 0000000..da0da48 --- /dev/null +++ b/protosV2/src/main/proto/apemanager/service.proto @@ -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; +} \ No newline at end of file diff --git a/protosV2/src/main/proto/apemanager/types.proto b/protosV2/src/main/proto/apemanager/types.proto new file mode 100644 index 0000000..644a8b5 --- /dev/null +++ b/protosV2/src/main/proto/apemanager/types.proto @@ -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; + } +} diff --git a/protosV2/src/main/proto/container/service.proto b/protosV2/src/main/proto/container/service.proto new file mode 100644 index 0000000..ce7634a --- /dev/null +++ b/protosV2/src/main/proto/container/service.proto @@ -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; +} diff --git a/protosV2/src/main/proto/container/types.proto b/protosV2/src/main/proto/container/types.proto new file mode 100644 index 0000000..128a0bd --- /dev/null +++ b/protosV2/src/main/proto/container/types.proto @@ -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" ]; +} diff --git a/protosV2/src/main/proto/lock/types.proto b/protosV2/src/main/proto/lock/types.proto new file mode 100644 index 0000000..85766db --- /dev/null +++ b/protosV2/src/main/proto/lock/types.proto @@ -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" ]; +} diff --git a/protosV2/src/main/proto/netmap/service.proto b/protosV2/src/main/proto/netmap/service.proto new file mode 100644 index 0000000..ac4f4cc --- /dev/null +++ b/protosV2/src/main/proto/netmap/service.proto @@ -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; +} diff --git a/protosV2/src/main/proto/netmap/types.proto b/protosV2/src/main/proto/netmap/types.proto new file mode 100644 index 0000000..1ff2e9e --- /dev/null +++ b/protosV2/src/main/proto/netmap/types.proto @@ -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" ]; +} diff --git a/protosV2/src/main/proto/object/service.proto b/protosV2/src/main/proto/object/service.proto new file mode 100644 index 0000000..98ce6f0 --- /dev/null +++ b/protosV2/src/main/proto/object/service.proto @@ -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; +} \ No newline at end of file diff --git a/protosV2/src/main/proto/object/types.proto b/protosV2/src/main/proto/object/types.proto new file mode 100644 index 0000000..3162270 --- /dev/null +++ b/protosV2/src/main/proto/object/types.proto @@ -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; +} \ No newline at end of file diff --git a/protosV2/src/main/proto/refs/types.proto b/protosV2/src/main/proto/refs/types.proto new file mode 100644 index 0000000..30cb552 --- /dev/null +++ b/protosV2/src/main/proto/refs/types.proto @@ -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" ]; +} diff --git a/protosV2/src/main/proto/session/service.proto b/protosV2/src/main/proto/session/service.proto new file mode 100644 index 0000000..84cbff4 --- /dev/null +++ b/protosV2/src/main/proto/session/service.proto @@ -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; +} diff --git a/protosV2/src/main/proto/session/types.proto b/protosV2/src/main/proto/session/types.proto new file mode 100644 index 0000000..2b33386 --- /dev/null +++ b/protosV2/src/main/proto/session/types.proto @@ -0,0 +1,238 @@ +syntax = "proto3"; + +package neo.fs.v2.session; + +option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session/grpc;session"; +option java_package = "frostFS.session"; + +import "refs/types.proto"; +import "acl/types.proto"; +import "status/types.proto"; + +// Context information for Session Tokens related to ObjectService requests +message ObjectSessionContext { + // Object request verbs + enum Verb { + // Unknown verb + VERB_UNSPECIFIED = 0; + + // Refers to object.Put RPC call + PUT = 1; + + // Refers to object.Get RPC call + GET = 2; + + // Refers to object.Head RPC call + HEAD = 3; + + // Refers to object.Search RPC call + SEARCH = 4; + + // Refers to object.Delete RPC call + DELETE = 5; + + // Refers to object.GetRange RPC call + RANGE = 6; + + // Refers to object.GetRangeHash RPC call + RANGEHASH = 7; + } + // Type of request for which the token is issued + Verb verb = 1 [ json_name = "verb" ]; + + // Carries objects involved in the object session. + message Target { + // Indicates which container the session is spread to. Field MUST be set + // and correct. + refs.ContainerID container = 1 [ json_name = "container" ]; + + // Indicates which objects the session is spread to. Objects are expected + // to be stored in the NeoFS container referenced by `container` field. + // Each element MUST have correct format. + repeated refs.ObjectID objects = 2 [ json_name = "objects" ]; + } + // Object session target. MUST be correctly formed and set. If `objects` + // field is not empty, then the session applies only to these elements, + // otherwise, to all objects from the specified container. + Target target = 2 [ json_name = "target" ]; +} + +// Context information for Session Tokens related to ContainerService requests. +message ContainerSessionContext { + // Container request verbs + enum Verb { + // Unknown verb + VERB_UNSPECIFIED = 0; + + // Refers to container.Put RPC call + PUT = 1; + + // Refers to container.Delete RPC call + DELETE = 2; + + // Refers to container.SetExtendedACL RPC call + SETEACL = 3; + } + // Type of request for which the token is issued + Verb verb = 1 [ json_name = "verb" ]; + + // Spreads the action to all owner containers. + // If set, container_id field is ignored. + bool wildcard = 2 [ json_name = "wildcard" ]; + + // Particular container to which the action applies. + // Ignored if wildcard flag is set. + refs.ContainerID container_id = 3 [ json_name = "containerID" ]; +} + +// NeoFS Session Token. +message SessionToken { + // Session Token body + message Body { + // Token identifier is a valid UUIDv4 in binary form + bytes id = 1 [ json_name = "id" ]; + + // Identifier of the session initiator + neo.fs.v2.refs.OwnerID owner_id = 2 [ json_name = "ownerID" ]; + + // Lifetime parameters of the token. Field names taken from rfc7519. + message TokenLifetime { + // Expiration Epoch + uint64 exp = 1 [ json_name = "exp" ]; + + // Not valid before Epoch + uint64 nbf = 2 [ json_name = "nbf" ]; + + // Issued at Epoch + uint64 iat = 3 [ json_name = "iat" ]; + } + // Lifetime of the session + TokenLifetime lifetime = 3 [ json_name = "lifetime" ]; + + // Public key used in session + bytes session_key = 4 [ json_name = "sessionKey" ]; + + // Session Context information + oneof context { + // ObjectService session context + ObjectSessionContext object = 5 [ json_name = "object" ]; + + // ContainerService session context + ContainerSessionContext container = 6 [ json_name = "container" ]; + } + } + // Session Token contains the proof of trust between peers to be attached in + // requests for further verification. Please see corresponding section of + // NeoFS Technical Specification for details. + Body body = 1 [ json_name = "body" ]; + + // Signature of `SessionToken` information + neo.fs.v2.refs.Signature signature = 2 [ json_name = "signature" ]; +} + +// Extended headers for Request/Response. They may contain any user-defined +// headers to be interpreted on application level. +// +// Key name must be a unique valid UTF-8 string. Value can't be empty. Requests +// or Responses with duplicated header names or headers with empty values will +// be considered invalid. +// +// There are some "well-known" headers starting with `__SYSTEM__` (`__NEOFS__` +// is deprecated) prefix that affect system behaviour: +// +// * [ __SYSTEM__NETMAP_EPOCH ] \ +// (`__NEOFS__NETMAP_EPOCH` is deprecated) \ +// Netmap epoch to use for object placement calculation. The `value` is string +// encoded `uint64` in decimal presentation. If set to '0' or not set, the +// current epoch only will be used. +// * [ __SYSTEM__NETMAP_LOOKUP_DEPTH ] \ +// (`__NEOFS__NETMAP_LOOKUP_DEPTH` is deprecated) \ +// If object can't be found using current epoch's netmap, this header limits +// how many past epochs the node can look up through. The `value` is string +// encoded `uint64` in decimal presentation. If set to '0' or not set, only +// the current epoch will be used. +message XHeader { + // Key of the X-Header + string key = 1 [ json_name = "key" ]; + + // Value of the X-Header + string value = 2 [ json_name = "value" ]; +} + +// Meta information attached to the request. When forwarded between peers, +// request meta headers are folded in matryoshka style. +message RequestMetaHeader { + // Peer's API version used + neo.fs.v2.refs.Version version = 1 [ json_name = "version" ]; + + // Peer's local epoch number. Set to 0 if unknown. + uint64 epoch = 2 [ json_name = "epoch" ]; + + // Maximum number of intermediate nodes in the request route + uint32 ttl = 3 [ json_name = "ttl" ]; + + // Request X-Headers + repeated XHeader x_headers = 4 [ json_name = "xHeaders" ]; + + // Session token within which the request is sent + SessionToken session_token = 5 [ json_name = "sessionToken" ]; + + // `BearerToken` with eACL overrides for the request + neo.fs.v2.acl.BearerToken bearer_token = 6 [ json_name = "bearerToken" ]; + + // `RequestMetaHeader` of the origin request + RequestMetaHeader origin = 7 [ json_name = "origin" ]; + + // NeoFS network magic. Must match the value for the network + // that the server belongs to. + uint64 magic_number = 8 [ json_name = "magicNumber" ]; +} + +// Information about the response +message ResponseMetaHeader { + // Peer's API version used + neo.fs.v2.refs.Version version = 1 [ json_name = "version" ]; + + // Peer's local epoch number + uint64 epoch = 2 [ json_name = "epoch" ]; + + // Maximum number of intermediate nodes in the request route + uint32 ttl = 3 [ json_name = "ttl" ]; + + // Response X-Headers + repeated XHeader x_headers = 4 [ json_name = "xHeaders" ]; + + // `ResponseMetaHeader` of the origin request + ResponseMetaHeader origin = 5 [ json_name = "origin" ]; + + // Status return + neo.fs.v2.status.Status status = 6 [ json_name = "status" ]; +} + +// Verification info for the request signed by all intermediate nodes. +message RequestVerificationHeader { + // Request Body signature. Should be generated once by the request initiator. + neo.fs.v2.refs.Signature body_signature = 1 [ json_name = "bodySignature" ]; + // Request Meta signature is added and signed by each intermediate node + neo.fs.v2.refs.Signature meta_signature = 2 [ json_name = "metaSignature" ]; + // Signature of previous hops + neo.fs.v2.refs.Signature origin_signature = 3 + [ json_name = "originSignature" ]; + + // Chain of previous hops signatures + RequestVerificationHeader origin = 4 [ json_name = "origin" ]; +} + +// Verification info for the response signed by all intermediate nodes +message ResponseVerificationHeader { + // Response Body signature. Should be generated once by an answering node. + neo.fs.v2.refs.Signature body_signature = 1 [ json_name = "bodySignature" ]; + // Response Meta signature is added and signed by each intermediate node + neo.fs.v2.refs.Signature meta_signature = 2 [ json_name = "metaSignature" ]; + // Signature of previous hops + neo.fs.v2.refs.Signature origin_signature = 3 + [ json_name = "originSignature" ]; + + // Chain of previous hops signatures + ResponseVerificationHeader origin = 4 [ json_name = "origin" ]; +} diff --git a/protosV2/src/main/proto/status/types.proto b/protosV2/src/main/proto/status/types.proto new file mode 100644 index 0000000..b7be372 --- /dev/null +++ b/protosV2/src/main/proto/status/types.proto @@ -0,0 +1,157 @@ +syntax = "proto3"; + +package neo.fs.v2.status; + +option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status/grpc;status"; +option java_package = "frostFS.status"; + +// Declares the general format of the status returns of the NeoFS RPC protocol. +// Status is present in all response messages. Each RPC of NeoFS protocol +// describes the possible outcomes and details of the operation. +// +// Each status is assigned a one-to-one numeric code. Any unique result of an +// operation in NeoFS is unambiguously associated with the code value. +// +// Numerical set of codes is split into 1024-element sections. An enumeration +// is defined for each section. Values can be referred to in the following ways: +// +// * numerical value ranging from 0 to 4,294,967,295 (global code); +// +// * values from enumeration (local code). The formula for the ratio of the +// local code (`L`) of a defined section (`S`) to the global one (`G`): +// `G = 1024 * S + L`. +// +// All outcomes are divided into successful and failed, which corresponds +// to the success or failure of the operation. The definition of success +// follows the semantics of RPC and the description of its purpose. +// The server must not attach code that is the opposite of the outcome type. +// +// See the set of return codes in the description for calls. +// +// Each status can carry a developer-facing error message. It should be a human +// readable text in English. The server should not transmit (and the client +// should not expect) useful information in the message. Field `details` +// should make the return more detailed. +message Status { + // The status code + uint32 code = 1; + + // Developer-facing error message + string message = 2; + + // Return detail. It contains additional information that can be used to + // analyze the response. Each code defines a set of details that can be + // attached to a status. Client should not handle details that are not + // covered by the code. + message Detail { + // Detail ID. The identifier is required to determine the binary format + // of the detail and how to decode it. + uint32 id = 1; + + // Binary status detail. Must follow the format associated with ID. + // The possibility of missing a value must be explicitly allowed. + bytes value = 2; + } + + // Data detailing the outcome of the operation. Must be unique by ID. + repeated Detail details = 3; +} + +// Section identifiers. +enum Section { + // Successful return codes. + SECTION_SUCCESS = 0; + + // Failure codes regardless of the operation. + SECTION_FAILURE_COMMON = 1; + + // Object service-specific errors. + SECTION_OBJECT = 2; + + // Container service-specific errors. + SECTION_CONTAINER = 3; + + // Session service-specific errors. + SECTION_SESSION = 4; + + // Session service-specific errors. + SECTION_APE_MANAGER = 5; +} + +// Section of NeoFS successful return codes. +enum Success { + // [**0**] Default success. Not detailed. + // If the server cannot match successful outcome to the code, it should + // use this code. + OK = 0; +} + +// Section of failed statuses independent of the operation. +enum CommonFail { + // [**1024**] Internal server error, default failure. Not detailed. + // If the server cannot match failed outcome to the code, it should + // use this code. + INTERNAL = 0; + + // [**1025**] Wrong magic of the NeoFS network. + // Details: + // - [**0**] Magic number of the served NeoFS network (big-endian 64-bit + // unsigned integer). + WRONG_MAGIC_NUMBER = 1; + + // [**1026**] Signature verification failure. + SIGNATURE_VERIFICATION_FAIL = 2; + + // [**1027**] Node is under maintenance. + NODE_UNDER_MAINTENANCE = 3; +} + +// Section of statuses for object-related operations. +enum Object { + // [**2048**] Access denied by ACL. + // Details: + // - [**0**] Human-readable description (UTF-8 encoded string). + ACCESS_DENIED = 0; + + // [**2049**] Object not found. + OBJECT_NOT_FOUND = 1; + + // [**2050**] Operation rejected by the object lock. + LOCKED = 2; + + // [**2051**] Locking an object with a non-REGULAR type rejected. + LOCK_NON_REGULAR_OBJECT = 3; + + // [**2052**] Object has been marked deleted. + OBJECT_ALREADY_REMOVED = 4; + + // [**2053**] Invalid range has been requested for an object. + OUT_OF_RANGE = 5; +} + +// Section of statuses for container-related operations. +enum Container { + // [**3072**] Container not found. + CONTAINER_NOT_FOUND = 0; + + // [**3073**] eACL table not found. + EACL_NOT_FOUND = 1; + + // [**3074**] Container access denied. + CONTAINER_ACCESS_DENIED = 2; +} + +// Section of statuses for session-related operations. +enum Session { + // [**4096**] Token not found. + TOKEN_NOT_FOUND = 0; + + // [**4097**] Token has expired. + TOKEN_EXPIRED = 1; +} + +// Section of status for APE manager related operations. +enum APEManager { + // [**5120**] The operation is denied by APE manager. + APE_MANAGER_ACCESS_DENIED = 0; +} \ No newline at end of file diff --git a/protosV2/src/main/proto/tombstone/types.proto b/protosV2/src/main/proto/tombstone/types.proto new file mode 100644 index 0000000..3e821c1 --- /dev/null +++ b/protosV2/src/main/proto/tombstone/types.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package neo.fs.v2.tombstone; + +option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/tombstone/grpc;tombstone"; +option java_package = "frostFS.tombstone"; + +import "refs/types.proto"; + +// Tombstone keeps record of deleted objects for a few epochs until they are +// purged from the NeoFS network. +message Tombstone { + // Last NeoFS epoch number of the tombstone lifetime. It's set by the + // tombstone creator depending on the current NeoFS network settings. A + // tombstone object must have the same expiration epoch value in + // `__SYSTEM__EXPIRATION_EPOCH` (`__NEOFS__EXPIRATION_EPOCH` is deprecated) + // attribute. Otherwise, the tombstone will be rejected by a storage node. + uint64 expiration_epoch = 1 [ json_name = "expirationEpoch" ]; + + // 16 byte UUID used to identify the split object hierarchy parts. Must be + // unique inside a container. All objects participating in the split must + // have the same `split_id` value. + bytes split_id = 2 [ json_name = "splitID" ]; + + // List of objects to be deleted. + repeated neo.fs.v2.refs.ObjectID members = 3 [ json_name = "members" ]; +}