From dedf65bd4b65324b22909e5f40220db15443a5b7 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 22 Jan 2022 20:24:37 +0000 Subject: [PATCH] Added initial protocol methods for metadata requests and forwarding. Not tested yet. --- .../api/resource/ArbitraryResource.java | 39 ++ .../org/qortal/controller/Controller.java | 17 + .../ArbitraryDataFileListManager.java | 4 +- .../arbitrary/ArbitraryDataManager.java | 3 + .../arbitrary/ArbitraryMetadataManager.java | 434 ++++++++++++++++++ .../message/ArbitraryMetadataMessage.java | 95 ++++ .../message/GetArbitraryMetadataMessage.java | 83 ++++ .../org/qortal/network/message/Message.java | 5 +- 8 files changed, 677 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java create mode 100644 src/main/java/org/qortal/network/message/ArbitraryMetadataMessage.java create mode 100644 src/main/java/org/qortal/network/message/GetArbitraryMetadataMessage.java diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index d80d3bd7..6e71689c 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.*; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -33,10 +34,12 @@ import org.qortal.api.resource.TransactionsResource.ConfirmationStatus; import org.qortal.arbitrary.*; import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType; import org.qortal.arbitrary.exception.MissingDataException; +import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; import org.qortal.controller.Controller; import org.qortal.controller.arbitrary.ArbitraryDataStorageManager; +import org.qortal.controller.arbitrary.ArbitraryMetadataManager; import org.qortal.data.account.AccountData; import org.qortal.data.arbitrary.ArbitraryCategoryInfo; import org.qortal.data.arbitrary.ArbitraryResourceInfo; @@ -634,6 +637,42 @@ public class ArbitraryResource { } + // Metadata + + @GET + @Path("/metadata/{service}/{name}/{identifier}") + @Operation( + summary = "Fetch raw metadata from resource with supplied service, name, identifier, and relative path", + responses = { + @ApiResponse( + description = "Path to file structure containing requested data", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ArbitraryDataTransactionMetadata.class + ) + ) + ) + } + ) + @SecurityRequirement(name = "apiKey") + public String getMetadata(@HeaderParam(Security.API_KEY_HEADER) String apiKey, + @PathParam("service") Service service, + @PathParam("name") String name, + @PathParam("identifier") String identifier) { + Security.checkApiCallAllowed(request); + + ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier); + + byte[] metadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource); + if (metadata != null) { + return new String(metadata, StandardCharsets.UTF_8); + } + + return null; + } + + // Upload data at supplied path diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 2bfc80c2..645fb2ae 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -221,6 +221,15 @@ public class Controller extends Thread { } public GetArbitraryDataFileListMessageStats getArbitraryDataFileListMessageStats = new GetArbitraryDataFileListMessageStats(); + public static class GetArbitraryMetadataMessageStats { + public AtomicLong requests = new AtomicLong(); + public AtomicLong unknownFiles = new AtomicLong(); + + public GetArbitraryMetadataMessageStats() { + } + } + public GetArbitraryMetadataMessageStats getArbitraryMetadataMessageStats = new GetArbitraryMetadataMessageStats(); + public AtomicLong latestBlocksCacheRefills = new AtomicLong(); public StatsSnapshot() { @@ -1396,6 +1405,14 @@ public class Controller extends Thread { ArbitraryDataManager.getInstance().onNetworkArbitrarySignaturesMessage(peer, message); break; + case GET_ARBITRARY_METADATA: + ArbitraryMetadataManager.getInstance().onNetworkGetArbitraryMetadataMessage(peer, message); + break; + + case ARBITRARY_METADATA: + ArbitraryMetadataManager.getInstance().onNetworkArbitraryMetadataMessage(peer, message); + break; + default: LOGGER.debug(() -> String.format("Unhandled %s message [ID %d] from peer %s", message.getType().name(), message.getId(), peer)); break; diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java index a1dc5d21..6ef95b59 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java @@ -57,9 +57,9 @@ public class ArbitraryDataFileListManager { /** Maximum number of seconds that a file list relay request is able to exist on the network */ - private static long RELAY_REQUEST_MAX_DURATION = 5000L; + public static long RELAY_REQUEST_MAX_DURATION = 5000L; /** Maximum number of hops that a file list relay request is allowed to make */ - private static int RELAY_REQUEST_MAX_HOPS = 3; + public static int RELAY_REQUEST_MAX_HOPS = 3; private ArbitraryDataFileListManager() { diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java index 20b4885a..b83b19f1 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java @@ -275,6 +275,9 @@ public class ArbitraryDataManager extends Thread { // Cleanup file request caches ArbitraryDataFileManager.getInstance().cleanupRequestCache(now); + + // Clean up metadata request caches + ArbitraryMetadataManager.getInstance().cleanupRequestCache(now); } public boolean isResourceCached(ArbitraryDataResource resource) { diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java new file mode 100644 index 00000000..7e0ed6e5 --- /dev/null +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java @@ -0,0 +1,434 @@ +package org.qortal.controller.arbitrary; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.arbitrary.ArbitraryDataFile; +import org.qortal.arbitrary.ArbitraryDataResource; +import org.qortal.controller.Controller; +import org.qortal.data.transaction.ArbitraryTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.network.Network; +import org.qortal.network.Peer; +import org.qortal.network.message.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.utils.Base58; +import org.qortal.utils.NTP; +import org.qortal.utils.Triple; + +import java.util.*; + +import static org.qortal.controller.arbitrary.ArbitraryDataFileListManager.RELAY_REQUEST_MAX_DURATION; +import static org.qortal.controller.arbitrary.ArbitraryDataFileListManager.RELAY_REQUEST_MAX_HOPS; + +public class ArbitraryMetadataManager { + + private static final Logger LOGGER = LogManager.getLogger(ArbitraryMetadataManager.class); + + private static ArbitraryMetadataManager instance; + + /** + * Map of recent incoming requests for ARBITRARY transaction metadata. + *

+ * Key is original request's message ID
+ * Value is Triple<transaction signature in base58, first requesting peer, first request's timestamp> + *

+ * If peer is null then either:
+ *

+ * If signature is null then we have already received the file list and either:
+ * + */ + public Map> arbitraryMetadataRequests = Collections.synchronizedMap(new HashMap<>()); + + /** + * Map to keep track of in progress arbitrary metadata requests + * Key: string - the signature encoded in base58 + * Value: Triple + */ + private Map> arbitraryMetadataSignatureRequests = Collections.synchronizedMap(new HashMap<>()); + + + private ArbitraryMetadataManager() { + } + + public static ArbitraryMetadataManager getInstance() { + if (instance == null) + instance = new ArbitraryMetadataManager(); + + return instance; + } + + public void cleanupRequestCache(Long now) { + if (now == null) { + return; + } + final long requestMinimumTimestamp = now - ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT; + arbitraryMetadataRequests.entrySet().removeIf(entry -> entry.getValue().getC() == null || entry.getValue().getC() < requestMinimumTimestamp); + } + + + public byte[] fetchMetadata(ArbitraryDataResource arbitraryDataResource) { + try (final Repository repository = RepositoryManager.getRepository()) { + // Find latest transaction + ArbitraryTransactionData latestTransaction = repository.getArbitraryRepository() + .getLatestTransaction(arbitraryDataResource.getResourceId(), arbitraryDataResource.getService(), + null, arbitraryDataResource.getIdentifier()); + + if (latestTransaction != null) { + byte[] signature = latestTransaction.getSignature(); + byte[] metadataHash = latestTransaction.getMetadataHash(); + if (metadataHash == null) { + // This resource doesn't have metadata + return null; + } + + ArbitraryDataFile metadataFile = ArbitraryDataFile.fromHash(metadataHash, signature); + if (metadataFile.exists()) { + // Use local copy + return metadataFile.getBytes(); + } + else { + // Request from network + this.fetchArbitraryMetadata(latestTransaction); + } + } + + } catch (DataException e) { + LOGGER.error("Repository issue when fetching arbitrary transaction metadata", e); + } + + return null; + } + + + // Request metadata from network + + public boolean fetchArbitraryMetadata(ArbitraryTransactionData arbitraryTransactionData) { + byte[] signature = arbitraryTransactionData.getSignature(); + String signature58 = Base58.encode(signature); + + // Require an NTP sync + Long now = NTP.getTime(); + if (now == null) { + return false; + } + + // If we've already tried too many times in a short space of time, make sure to give up + if (!this.shouldMakeMetadataRequestForSignature(signature58)) { + LOGGER.trace("Skipping metadata request for signature {} due to rate limit", signature58); + return false; + } + this.addToSignatureRequests(signature58, true, false); + + List handshakedPeers = Network.getInstance().getHandshakedPeers(); + LOGGER.debug(String.format("Sending metadata request for signature %s to %d peers...", signature58, handshakedPeers.size())); + + // Build request + Message getArbitraryMetadataMessage = new GetArbitraryMetadataMessage(signature, now, 0); + + // Save our request into requests map + Triple requestEntry = new Triple<>(signature58, null, NTP.getTime()); + + // Assign random ID to this message + int id; + do { + id = new Random().nextInt(Integer.MAX_VALUE - 1) + 1; + + // Put queue into map (keyed by message ID) so we can poll for a response + // If putIfAbsent() doesn't return null, then this ID is already taken + } while (arbitraryMetadataRequests.put(id, requestEntry) != null); + getArbitraryMetadataMessage.setId(id); + + // Broadcast request + Network.getInstance().broadcast(peer -> getArbitraryMetadataMessage); + + // Poll to see if data has arrived + final long singleWait = 100; + long totalWait = 0; + while (totalWait < ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT) { + try { + Thread.sleep(singleWait); + } catch (InterruptedException e) { + break; + } + + requestEntry = arbitraryMetadataRequests.get(id); + if (requestEntry == null) + return false; + + if (requestEntry.getA() == null) + break; + + totalWait += singleWait; + } + return true; + } + + + // Track metadata lookups by signature + + private boolean shouldMakeMetadataRequestForSignature(String signature58) { + Triple request = arbitraryMetadataSignatureRequests.get(signature58); + + if (request == null) { + // Not attempted yet + return true; + } + + // Extract the components + Integer networkBroadcastCount = request.getA(); + // Integer directPeerRequestCount = request.getB(); + Long lastAttemptTimestamp = request.getC(); + + if (lastAttemptTimestamp == null) { + // Not attempted yet + return true; + } + + long timeSinceLastAttempt = NTP.getTime() - lastAttemptTimestamp; + + // Allow a second attempt after 15 seconds, and another after 30 seconds + if (timeSinceLastAttempt > 15 * 1000L) { + // We haven't tried for at least 15 seconds + + if (networkBroadcastCount < 3) { + // We've made less than 3 total attempts + return true; + } + } + + // Then allow another 5 attempts, each 5 minutes apart + if (timeSinceLastAttempt > 5 * 60 * 1000L) { + // We haven't tried for at least 5 minutes + + if (networkBroadcastCount < 5) { + // We've made less than 5 total attempts + return true; + } + } + + // From then on, only try once every 24 hours, to reduce network spam + if (timeSinceLastAttempt > 24 * 60 * 60 * 1000L) { + // We haven't tried for at least 24 hours + return true; + } + + return false; + } + + public boolean isSignatureRateLimited(byte[] signature) { + String signature58 = Base58.encode(signature); + return !this.shouldMakeMetadataRequestForSignature(signature58); + } + + public long lastRequestForSignature(byte[] signature) { + String signature58 = Base58.encode(signature); + Triple request = arbitraryMetadataSignatureRequests.get(signature58); + + if (request == null) { + // Not attempted yet + return 0; + } + + // Extract the components + Long lastAttemptTimestamp = request.getC(); + if (lastAttemptTimestamp != null) { + return lastAttemptTimestamp; + } + return 0; + } + + public void addToSignatureRequests(String signature58, boolean incrementNetworkRequests, boolean incrementPeerRequests) { + Triple request = arbitraryMetadataSignatureRequests.get(signature58); + Long now = NTP.getTime(); + + if (request == null) { + // No entry yet + Triple newRequest = new Triple<>(0, 0, now); + arbitraryMetadataSignatureRequests.put(signature58, newRequest); + } + else { + // There is an existing entry + if (incrementNetworkRequests) { + request.setA(request.getA() + 1); + } + if (incrementPeerRequests) { + request.setB(request.getB() + 1); + } + request.setC(now); + arbitraryMetadataSignatureRequests.put(signature58, request); + } + } + + public void removeFromSignatureRequests(String signature58) { + arbitraryMetadataSignatureRequests.remove(signature58); + } + + + // Network handlers + + public void onNetworkArbitraryMetadataMessage(Peer peer, Message message) { + // Don't process if QDN is disabled + if (!Settings.getInstance().isQdnEnabled()) { + return; + } + + ArbitraryMetadataMessage arbitraryMetadataMessage = (ArbitraryMetadataMessage) message; + LOGGER.debug("Received metadata from peer {}", peer); + + // Do we have a pending request for this data? + Triple request = arbitraryMetadataRequests.get(message.getId()); + if (request == null || request.getA() == null) { + return; + } + boolean isRelayRequest = (request.getB() != null); + + // Does this message's signature match what we're expecting? + byte[] signature = arbitraryMetadataMessage.getSignature(); + String signature58 = Base58.encode(signature); + if (!request.getA().equals(signature58)) { + return; + } + + ArbitraryTransactionData arbitraryTransactionData = null; + ArbitraryDataFileManager arbitraryDataFileManager = ArbitraryDataFileManager.getInstance(); + + // Forwarding + if (isRelayRequest && Settings.getInstance().isRelayModeEnabled()) { + + // Get transaction info + try (final Repository repository = RepositoryManager.getRepository()) { + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (!(transactionData instanceof ArbitraryTransactionData)) + return; + arbitraryTransactionData = (ArbitraryTransactionData) transactionData; + } catch (DataException e) { + LOGGER.error(String.format("Repository issue while finding arbitrary transaction metadata for peer %s", peer), e); + } + + // Check if the name is blocked + boolean isBlocked = (arbitraryTransactionData == null || ArbitraryDataStorageManager.getInstance().isNameBlocked(arbitraryTransactionData.getName())); + if (!isBlocked) { + Peer requestingPeer = request.getB(); + if (requestingPeer != null) { + + // Forward to requesting peer + LOGGER.debug("Forwarding metadata to requesting peer: {}", requestingPeer); + if (!requestingPeer.sendMessage(arbitraryMetadataMessage)) { + requestingPeer.disconnect("failed to forward arbitrary metadata"); + } + } + } + } + } + + public void onNetworkGetArbitraryMetadataMessage(Peer peer, Message message) { + // Don't respond if QDN is disabled + if (!Settings.getInstance().isQdnEnabled()) { + return; + } + + Controller.getInstance().stats.getArbitraryMetadataMessageStats.requests.incrementAndGet(); + + GetArbitraryMetadataMessage getArbitraryMetadataMessage = (GetArbitraryMetadataMessage) message; + byte[] signature = getArbitraryMetadataMessage.getSignature(); + String signature58 = Base58.encode(signature); + Long now = NTP.getTime(); + Triple newEntry = new Triple<>(signature58, peer, now); + + // If we've seen this request recently, then ignore + if (arbitraryMetadataRequests.putIfAbsent(message.getId(), newEntry) != null) { + LOGGER.debug("Ignoring metadata request from peer {} for signature {}", peer, signature58); + return; + } + + LOGGER.debug("Received metadata request from peer {} for signature {}", peer, signature58); + + ArbitraryTransactionData transactionData = null; + ArbitraryDataFile metadataFile = null; + + try (final Repository repository = RepositoryManager.getRepository()) { + + // Firstly we need to lookup this file on chain to get its metadata hash + transactionData = (ArbitraryTransactionData)repository.getTransactionRepository().fromSignature(signature); + if (transactionData instanceof ArbitraryTransactionData) { + + // Check if we're even allowed to serve metadata for this transaction + if (ArbitraryDataStorageManager.getInstance().canStoreData(transactionData)) { + + byte[] metadataHash = transactionData.getMetadataHash(); + if (metadataHash != null) { + + // Load metadata file + metadataFile = ArbitraryDataFile.fromHash(metadataHash, signature); + } + } + } + + } catch (DataException e) { + LOGGER.error(String.format("Repository issue while fetching arbitrary metadata for peer %s", peer), e); + } + + // We should only respond if we have the metadata file + if (metadataFile != null && metadataFile.exists()) { + + // We have the metadata file, so update requests map to reflect that we've sent it + newEntry = new Triple<>(null, null, now); + arbitraryMetadataRequests.put(message.getId(), newEntry); + + ArbitraryMetadataMessage arbitraryMetadataMessage = new ArbitraryMetadataMessage(signature, metadataFile); + arbitraryMetadataMessage.setId(message.getId()); + if (!peer.sendMessage(arbitraryMetadataMessage)) { + LOGGER.debug("Couldn't send metadata"); + peer.disconnect("failed to send metadata"); + return; + } + LOGGER.debug("Sent metadata"); + + // Nothing left to do, so return to prevent any unnecessary forwarding from occurring + LOGGER.debug("No need for any forwarding because metadata request is fully served"); + return; + + } + + // We may need to forward this request on + boolean isBlocked = (transactionData == null || ArbitraryDataStorageManager.getInstance().isNameBlocked(transactionData.getName())); + if (Settings.getInstance().isRelayModeEnabled() && !isBlocked) { + // In relay mode - so ask our other peers if they have it + + long requestTime = getArbitraryMetadataMessage.getRequestTime(); + int requestHops = getArbitraryMetadataMessage.getRequestHops(); + getArbitraryMetadataMessage.setRequestHops(++requestHops); + long totalRequestTime = now - requestTime; + + if (totalRequestTime < RELAY_REQUEST_MAX_DURATION) { + // Relay request hasn't timed out yet, so can potentially be rebroadcast + if (requestHops < RELAY_REQUEST_MAX_HOPS) { + // Relay request hasn't reached the maximum number of hops yet, so can be rebroadcast + + LOGGER.debug("Rebroadcasting metadata request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops); + Network.getInstance().broadcast( + broadcastPeer -> broadcastPeer == peer || + Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost()) + ? null : getArbitraryMetadataMessage); + + } + else { + // This relay request has reached the maximum number of allowed hops + } + } + else { + // This relay request has timed out + } + } + } + +} diff --git a/src/main/java/org/qortal/network/message/ArbitraryMetadataMessage.java b/src/main/java/org/qortal/network/message/ArbitraryMetadataMessage.java new file mode 100644 index 00000000..9228d458 --- /dev/null +++ b/src/main/java/org/qortal/network/message/ArbitraryMetadataMessage.java @@ -0,0 +1,95 @@ +package org.qortal.network.message; + +import com.google.common.primitives.Ints; +import org.qortal.arbitrary.ArbitraryDataFile; +import org.qortal.repository.DataException; +import org.qortal.transform.Transformer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +public class ArbitraryMetadataMessage extends Message { + + private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH; + + private final byte[] signature; + private final ArbitraryDataFile arbitraryMetadataFile; + + public ArbitraryMetadataMessage(byte[] signature, ArbitraryDataFile arbitraryDataFile) { + super(MessageType.ARBITRARY_METADATA); + + this.signature = signature; + this.arbitraryMetadataFile = arbitraryDataFile; + } + + public ArbitraryMetadataMessage(int id, byte[] signature, ArbitraryDataFile arbitraryDataFile) { + super(id, MessageType.ARBITRARY_METADATA); + + this.signature = signature; + this.arbitraryMetadataFile = arbitraryDataFile; + } + + public byte[] getSignature() { + return this.signature; + } + + public ArbitraryDataFile getArbitraryMetadataFile() { + return this.arbitraryMetadataFile; + } + + public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException { + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + int dataLength = byteBuffer.getInt(); + + if (byteBuffer.remaining() != dataLength) + return null; + + byte[] data = new byte[dataLength]; + byteBuffer.get(data); + + try { + ArbitraryDataFile arbitraryMetadataFile = new ArbitraryDataFile(data, signature); + return new ArbitraryMetadataMessage(id, signature, arbitraryMetadataFile); + } + catch (DataException e) { + return null; + } + } + + @Override + protected byte[] toData() { + if (this.arbitraryMetadataFile == null) { + return null; + } + + byte[] data = this.arbitraryMetadataFile.getBytes(); + if (data == null) { + return null; + } + + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(signature); + + bytes.write(Ints.toByteArray(data.length)); + + bytes.write(data); + + return bytes.toByteArray(); + } catch (IOException e) { + return null; + } + } + + public ArbitraryMetadataMessage cloneWithNewId(int newId) { + ArbitraryMetadataMessage clone = new ArbitraryMetadataMessage(this.signature, this.arbitraryMetadataFile); + clone.setId(newId); + return clone; + } + +} diff --git a/src/main/java/org/qortal/network/message/GetArbitraryMetadataMessage.java b/src/main/java/org/qortal/network/message/GetArbitraryMetadataMessage.java new file mode 100644 index 00000000..66c8f86c --- /dev/null +++ b/src/main/java/org/qortal/network/message/GetArbitraryMetadataMessage.java @@ -0,0 +1,83 @@ +package org.qortal.network.message; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import org.qortal.transform.Transformer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +import static org.qortal.transform.Transformer.INT_LENGTH; +import static org.qortal.transform.Transformer.LONG_LENGTH; + +public class GetArbitraryMetadataMessage extends Message { + + private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH; + + private final byte[] signature; + private final long requestTime; + private int requestHops; + + public GetArbitraryMetadataMessage(byte[] signature, long requestTime, int requestHops) { + this(-1, signature, requestTime, requestHops); + } + + private GetArbitraryMetadataMessage(int id, byte[] signature, long requestTime, int requestHops) { + super(id, MessageType.GET_ARBITRARY_METADATA); + + this.signature = signature; + this.requestTime = requestTime; + this.requestHops = requestHops; + } + + public byte[] getSignature() { + return this.signature; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException { + if (bytes.remaining() != SIGNATURE_LENGTH + LONG_LENGTH + INT_LENGTH) + return null; + + byte[] signature = new byte[SIGNATURE_LENGTH]; + + bytes.get(signature); + + long requestTime = bytes.getLong(); + + int requestHops = bytes.getInt(); + + return new GetArbitraryMetadataMessage(id, signature, requestTime, requestHops); + } + + @Override + protected byte[] toData() { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(this.signature); + + bytes.write(Longs.toByteArray(this.requestTime)); + + bytes.write(Ints.toByteArray(this.requestHops)); + + return bytes.toByteArray(); + } catch (IOException e) { + return null; + } + } + + public long getRequestTime() { + return this.requestTime; + } + + public int getRequestHops() { + return this.requestHops; + } + + public void setRequestHops(int requestHops) { + this.requestHops = requestHops; + } + +} diff --git a/src/main/java/org/qortal/network/message/Message.java b/src/main/java/org/qortal/network/message/Message.java index c7657493..10c22efe 100644 --- a/src/main/java/org/qortal/network/message/Message.java +++ b/src/main/java/org/qortal/network/message/Message.java @@ -91,7 +91,10 @@ public abstract class Message { ARBITRARY_DATA_FILE_LIST(120), GET_ARBITRARY_DATA_FILE_LIST(121), - ARBITRARY_SIGNATURES(130); + ARBITRARY_SIGNATURES(130), + + ARBITRARY_METADATA(130), + GET_ARBITRARY_METADATA(131); public final int value; public final Method fromByteBufferMethod;