diff --git a/.ci/api-current.txt b/.ci/api-current.txt index a34d83e643..2b58759903 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -5895,10 +5895,8 @@ public abstract class net.corda.core.transactions.FullTransaction extends net.co public abstract java.util.List> getReferences() ## @DoNotImplement -@CordaSerializable public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) - @DeprecatedConstructorForDeserialization public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters) @NotNull public final java.util.List> commandsOfType(Class) diff --git a/build.gradle b/build.gradle index 89192b9f23..6de950b67a 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,7 @@ buildscript { ext.disruptor_version = constants.getProperty("disruptorVersion") ext.metrics_version = constants.getProperty("metricsVersion") ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion") + ext.djvm_version = constants.getProperty("djvmVersion") ext.okhttp_version = '3.14.2' ext.netty_version = '4.1.22.Final' ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion") @@ -335,6 +336,7 @@ allprojects { jcenter() maven { url "$artifactory_contextUrl/corda-dependencies" } maven { url 'https://repo.gradle.org/gradle/libs-releases' } + maven { url "$artifactory_contextUrl/corda-dev" } } configurations { @@ -481,11 +483,13 @@ bintrayConfig { 'corda-core', 'corda-core-deterministic', 'corda-deterministic-verifier', + 'corda-deserializers-djvm', 'corda', 'corda-finance-workflows', 'corda-finance-contracts', 'corda-node', 'corda-node-api', + 'corda-node-djvm', 'corda-test-common', 'corda-test-utils', 'corda-test-db', @@ -498,6 +502,7 @@ bintrayConfig { 'corda-tools-shell-cli', 'corda-serialization', 'corda-serialization-deterministic', + 'corda-serialization-djvm', 'corda-tools-blob-inspector', 'corda-tools-explorer', 'corda-tools-network-bootstrapper', diff --git a/constants.properties b/constants.properties index 5d74edc050..c743f1fb05 100644 --- a/constants.properties +++ b/constants.properties @@ -13,11 +13,10 @@ java8MinUpdateVersion=171 platformVersion=5 guavaVersion=28.0-jre # Quasar version to use with Java 8: -quasarVersion=0.7.10 +quasarVersion=0.7.12_r3 quasarClassifier=jdk8 # Quasar version to use with Java 11: -quasarVersion11=0.8.0 -# Specify a classifier for Java 11 built artifacts +quasarVersion11=0.8.0_r3_rc1 jdkClassifier11=jdk11 proguardVersion=6.1.1 bouncycastleVersion=1.60 @@ -30,6 +29,7 @@ snakeYamlVersion=1.19 caffeineVersion=2.7.0 metricsVersion=4.1.0 metricsNewRelicVersion=1.1.1 +djvmVersion=1.0-RC01 openSourceBranch=https://github.com/corda/corda/blob/master openSourceSamplesBranch=https://github.com/corda/samples/blob/master jolokiaAgentVersion=1.6.1 diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index 4b84f32d7e..c1f65ebfa0 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -56,6 +56,7 @@ task patchCore(type: Zip, dependsOn: coreJarTask) { archiveExtension = 'jar' from(compileKotlin) + from(processResources) from(zipTree(originalJar)) { exclude 'net/corda/core/internal/*ToggleField*.class' exclude 'net/corda/core/serialization/*SerializationFactory*.class' diff --git a/core-deterministic/src/main/resources/META-INF/DJVM-preload b/core-deterministic/src/main/resources/META-INF/DJVM-preload new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt index f029be1f25..ac86cf8f6a 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt @@ -8,7 +8,6 @@ import net.corda.core.crypto.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.accessLeafIndex import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.serialization.deserialize @@ -307,37 +306,37 @@ class PartialMerkleTreeTest { val pmt = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19"))) // First leaf. - assertEquals(0, pmt.accessLeafIndex(SecureHash.sha256("0"))) + assertEquals(0, pmt.leafIndex(SecureHash.sha256("0"))) // Second leaf. - assertEquals(1, pmt.accessLeafIndex(SecureHash.sha256("1"))) + assertEquals(1, pmt.leafIndex(SecureHash.sha256("1"))) // A random leaf. - assertEquals(5, pmt.accessLeafIndex(SecureHash.sha256("5"))) + assertEquals(5, pmt.leafIndex(SecureHash.sha256("5"))) // The last leaf. - assertEquals(19, pmt.accessLeafIndex(SecureHash.sha256("19"))) + assertEquals(19, pmt.leafIndex(SecureHash.sha256("19"))) // The provided hash is not in the tree. - assertFailsWith { pmt.accessLeafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmt.leafIndex(SecureHash.sha256("10")) } // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). - assertFailsWith { pmt.accessLeafIndex(SecureHash.sha256("30")) } + assertFailsWith { pmt.leafIndex(SecureHash.sha256("30")) } val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("0"))) - assertEquals(0, pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0"))) // The provided hash is not in the tree. - assertFailsWith { pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) } val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("19"))) - assertEquals(19, pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19"))) // The provided hash is not in the tree. - assertFailsWith { pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) } val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("5"))) - assertEquals(5, pmtOneElement.accessLeafIndex(SecureHash.sha256("5"))) + assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5"))) // The provided hash is not in the tree. - assertFailsWith { pmtOneElement.accessLeafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmtOneElement.leafIndex(SecureHash.sha256("10")) } val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) - for (i in 0..19) assertEquals(i, pmtAllIncluded.accessLeafIndex(SecureHash.sha256(i.toString()))) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString()))) // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). - assertFailsWith { pmtAllIncluded.accessLeafIndex(SecureHash.sha256("30")) } + assertFailsWith { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) } } @Test diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index 1ef11c341b..f57d02d800 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -1,6 +1,7 @@ package net.corda.core.crypto import net.corda.core.CordaException +import net.corda.core.CordaInternal import net.corda.core.KeepForDJVM import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.serialization.CordaSerializable @@ -169,8 +170,9 @@ class PartialMerkleTree(val root: PartialTree) { * @return leaf-index of this component (starting from zero). * @throws MerkleTreeException if the provided hash is not in the tree. */ + @CordaInternal @Throws(MerkleTreeException::class) - internal fun leafIndex(leaf: SecureHash): Int { + fun leafIndex(leaf: SecureHash): Int { // Special handling if the tree consists of one node only. if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0 val flagPath = mutableListOf() diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt index 51b2794ddb..ff44f5140f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt @@ -1,12 +1,15 @@ package net.corda.core.internal import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException +import net.corda.core.crypto.SecureHash import net.corda.core.internal.rules.StateContractValidationEnforcementRule import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.contextLogger +import java.util.function.Function @DeleteForDJVM interface TransactionVerifierServiceInternal { @@ -25,15 +28,13 @@ fun LedgerTransaction.prepareVerify(extraAttachments: List) = this.i /** * Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the * wrong object instance. This class helps avoid that. - * - * @param inputVersions A map linking each contract class name to the advertised version of the JAR that defines it. Used for downgrade protection. */ -class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader) { +abstract class Verifier(val ltx: LedgerTransaction, protected val transactionClassLoader: ClassLoader) { private val inputStates: List> = ltx.inputs.map { it.state } private val allStates: List> = inputStates + ltx.references.map { it.state } + ltx.outputs companion object { - private val logger = contextLogger() + val logger = contextLogger() } /** @@ -140,7 +141,7 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C .withIndex() .filter { it.value.encumbrance != null } .map { Pair(it.index, it.value.encumbrance!!) } - if (!statesAndEncumbrance.isEmpty()) { + if (statesAndEncumbrance.isNotEmpty()) { checkBidirectionalOutputEncumbrances(statesAndEncumbrance) checkNotariesOutputEncumbrance(statesAndEncumbrance) } @@ -343,28 +344,56 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C } } + /** + * Placeholder function for the contract verification logic. + */ + abstract fun verifyContracts() +} + +class BasicVerifier(ltx: LedgerTransaction, transactionClassLoader: ClassLoader) : Verifier(ltx, transactionClassLoader) { /** * Check the transaction is contract-valid by running the verify() for each input and output state contract. * If any contract fails to verify, the whole transaction is considered to be invalid. * * Note: Reference states are not verified. */ - private fun verifyContracts() { - - // Loads the contract class from the transactionClassLoader. - fun contractClassFor(className: ContractClassName) = try { - transactionClassLoader.loadClass(className).asSubclass(Contract::class.java) - } catch (e: Exception) { - throw TransactionVerificationException.ContractCreationError(ltx.id, className, e) + override fun verifyContracts() { + try { + ContractVerifier(transactionClassLoader).apply(ltx) + } catch (e: TransactionVerificationException.ContractRejection) { + logger.error("Error validating transaction ${ltx.id}.", e.cause) + throw e } + } +} - val contractClasses: Map> = (inputStates + ltx.outputs) - .map { it.contract } - .toSet() - .map { contract -> contract to contractClassFor(contract) } - .toMap() +/** + * Verify all of the contracts on the given [LedgerTransaction]. + */ +@Suppress("TooGenericExceptionCaught") +@KeepForDJVM +class ContractVerifier(private val transactionClassLoader: ClassLoader) : Function { + // This constructor is used inside the DJVM's sandbox. + @Suppress("unused") + constructor() : this(ClassLoader.getSystemClassLoader()) - val contractInstances: List = contractClasses.map { (contractClassName, contractClass) -> + // Loads the contract class from the transactionClassLoader. + private fun createContractClass(id: SecureHash, contractClassName: ContractClassName): Class { + return try { + Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java) + } catch (e: Exception) { + throw TransactionVerificationException.ContractCreationError(id, contractClassName, e) + } + } + + override fun apply(ltx: LedgerTransaction) { + val contractClassNames = (ltx.inputs.map(StateAndRef::state) + ltx.outputs) + .map(TransactionState<*>::contract) + .toSet() + + contractClassNames.associateBy( + { it }, { createContractClass(ltx.id, it) } + ).map { (contractClassName, contractClass) -> try { /** * This function must execute with the DJVM's sandbox, which does not @@ -377,13 +406,10 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C } catch (e: Exception) { throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e) } - } - - contractInstances.forEach { contract -> + }.forEach { contract -> try { contract.verify(ltx) } catch (e: Exception) { - logger.error("Error validating transaction ${ltx.id}.", e) throw TransactionVerificationException.ContractRejection(ltx.id, contract, e) } } diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 41a5a4b8e9..a35a13b43b 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -10,6 +10,7 @@ import net.corda.core.flows.ContractUpgradeFlow import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.transactions.FilteredTransaction +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import java.security.PublicKey @@ -71,6 +72,12 @@ interface ServicesForResolution { */ @Throws(TransactionResolutionException::class, AttachmentResolutionException::class) fun loadContractAttachment(stateRef: StateRef): Attachment + + /** + * Provides a callback for the Node to customise the [LedgerTransaction]. + */ + @JvmDefault + fun specialise(ltx: LedgerTransaction): LedgerTransaction = ltx } /** diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt index 7060c110c3..f26e172d73 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -1,6 +1,8 @@ +@file:KeepForDJVM package net.corda.core.node.services import net.corda.core.DoNotImplement +import net.corda.core.KeepForDJVM import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt index e85e5f838f..0e5f1c5053 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt @@ -19,7 +19,6 @@ import java.io.IOException import java.io.InputStream import java.net.* import java.util.* -import java.util.jar.JarInputStream /** * A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only @@ -318,14 +317,14 @@ object AttachmentsClassLoaderBuilder { isAttachmentTrusted: (Attachment) -> Boolean, parent: ClassLoader = ClassLoader.getSystemClassLoader(), block: (ClassLoader) -> T): T { - val attachmentIds = attachments.map { it.id }.toSet() + val attachmentIds = attachments.map(Attachment::id).toSet() val serializationContext = cache.computeIfAbsent(Key(attachmentIds, params)) { // Create classloader and load serializers, whitelisted classes val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent) val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java) val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader) - .flatMap { it.whitelist } + .flatMap(SerializationWhitelist::whitelist) .toList() // Create a new serializationContext for the current transaction. In this context we will forbid diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 35120badac..4f6908ac2b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -3,18 +3,32 @@ package net.corda.core.transactions import net.corda.core.CordaInternal import net.corda.core.KeepForDJVM import net.corda.core.StubOutForDJVM -import net.corda.core.contracts.* +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.Command +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.CommandWithParties +import net.corda.core.contracts.ComponentGroupEnum +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.PrivacySalt +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.contracts.TransactionState +import net.corda.core.contracts.TransactionVerificationException import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party -import net.corda.core.internal.* +import net.corda.core.internal.BasicVerifier +import net.corda.core.internal.SerializedStateAndRef +import net.corda.core.internal.Verifier +import net.corda.core.internal.castIfPossible +import net.corda.core.internal.deserialiseCommands +import net.corda.core.internal.deserialiseComponentGroup +import net.corda.core.internal.isUploaderTrusted +import net.corda.core.internal.uncheckedCast import net.corda.core.node.NetworkParameters -import net.corda.core.serialization.ConstructorForDeserialization -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.utilities.contextLogger -import java.util.* +import java.util.Collections.unmodifiableList import java.util.function.Predicate /** @@ -39,10 +53,9 @@ import java.util.function.Predicate * * [LedgerTransaction]s should never be instantiated directly from client code, but rather via WireTransaction.toLedgerTransaction */ +@Suppress("LongParameterList") @KeepForDJVM -@CordaSerializable class LedgerTransaction -@ConstructorForDeserialization private constructor( // DOCSTART 1 /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ @@ -67,23 +80,35 @@ private constructor( */ override val networkParameters: NetworkParameters?, /** Referenced states, which are like inputs but won't be consumed. */ - override val references: List> + override val references: List>, //DOCEND 1 + + private val componentGroups: List?, + private val serializedInputs: List?, + private val serializedReferences: List?, + private val isAttachmentTrusted: (Attachment) -> Boolean, + private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier ) : FullTransaction() { - // These are not part of the c'tor above as that defines LedgerTransaction's serialisation format - private var componentGroups: List? = null - private var serializedInputs: List? = null - private var serializedReferences: List? = null - private var isAttachmentTrusted: (Attachment) -> Boolean = { it.isUploaderTrusted() } init { if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" } checkNotaryWhitelisted() } + @KeepForDJVM companion object { private val logger = contextLogger() + private fun protect(list: List?): List? { + return list?.run { + if (isEmpty()) { + emptyList() + } else { + unmodifiableList(this) + } + } + } + @CordaInternal internal fun create( inputs: List>, @@ -101,12 +126,58 @@ private constructor( serializedReferences: List? = null, isAttachmentTrusted: (Attachment) -> Boolean ): LedgerTransaction { - return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references).apply { - this.componentGroups = componentGroups - this.serializedInputs = serializedInputs - this.serializedReferences = serializedReferences - this.isAttachmentTrusted = isAttachmentTrusted - } + return LedgerTransaction( + inputs = inputs, + outputs = outputs, + commands = commands, + attachments = attachments, + id = id, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt, + networkParameters = networkParameters, + references = references, + componentGroups = protect(componentGroups), + serializedInputs = protect(serializedInputs), + serializedReferences = protect(serializedReferences), + isAttachmentTrusted = isAttachmentTrusted, + verifierFactory = ::BasicVerifier + ) + } + + /** + * This factory function will create an instance of [LedgerTransaction] + * that will be used inside the DJVM sandbox. + */ + @CordaInternal + fun createForSandbox( + inputs: List>, + outputs: List>, + commands: List>, + attachments: List, + id: SecureHash, + notary: Party?, + timeWindow: TimeWindow?, + privacySalt: PrivacySalt, + networkParameters: NetworkParameters, + references: List>): LedgerTransaction { + return LedgerTransaction( + inputs = inputs, + outputs = outputs, + commands = commands, + attachments = attachments, + id = id, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt, + networkParameters = networkParameters, + references = references, + componentGroups = null, + serializedInputs = null, + serializedReferences = null, + isAttachmentTrusted = { true }, + verifierFactory = ::BasicVerifier + ) } } @@ -144,17 +215,48 @@ private constructor( // Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules // like no-overlap, package namespace ownership and (in future) deterministic Java. return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext( - this.attachments + extraAttachments, + attachments + extraAttachments, getParamsWithGoo(), id, isAttachmentTrusted = isAttachmentTrusted) { transactionClassLoader -> // Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader]. // Only the copy will be used for verification, and the outer shell will be discarded. // This artifice is required to preserve backwards compatibility. - Verifier(createLtxForVerification(), transactionClassLoader) + verifierFactory(createLtxForVerification(), transactionClassLoader) } } + /** + * Pass all of this [LedgerTransaction] object's serialized state to a [transformer] function. + */ + @CordaInternal + fun transform(transformer: (List, List, List) -> T): T { + return transformer(componentGroups ?: emptyList(), serializedInputs ?: emptyList(), serializedReferences ?: emptyList()) + } + + /** + * We need a way to customise transaction verification inside the + * Node without changing either the wire format or any public APIs. + */ + @CordaInternal + fun specialise(alternateVerifier: (LedgerTransaction, ClassLoader) -> Verifier): LedgerTransaction = LedgerTransaction( + inputs = inputs, + outputs = outputs, + commands = commands, + attachments = attachments, + id = id, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt, + networkParameters = networkParameters, + references = references, + componentGroups = componentGroups, + serializedInputs = serializedInputs, + serializedReferences = serializedReferences, + isAttachmentTrusted = isAttachmentTrusted, + verifierFactory = alternateVerifier + ) + // Read network parameters with backwards compatibility goo. private fun getParamsWithGoo(): NetworkParameters { var params = networkParameters @@ -213,7 +315,12 @@ private constructor( timeWindow = this.timeWindow, privacySalt = this.privacySalt, networkParameters = this.networkParameters, - references = deserializedReferences + references = deserializedReferences, + componentGroups = componentGroups, + serializedInputs = serializedInputs, + serializedReferences = serializedReferences, + isAttachmentTrusted = isAttachmentTrusted, + verifierFactory = verifierFactory ) } else { // This branch is only present for backwards compatibility. @@ -582,10 +689,25 @@ private constructor( notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt - ) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null, emptyList()) + ) : this( + inputs = inputs, + outputs = outputs, + commands = commands, + attachments = attachments, + id = id, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt, + networkParameters = null, + references = emptyList(), + componentGroups = null, + serializedInputs = null, + serializedReferences = null, + isAttachmentTrusted = { it.isUploaderTrusted() }, + verifierFactory = ::BasicVerifier + ) @Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.") - @DeprecatedConstructorForDeserialization(1) constructor( inputs: List>, outputs: List>, @@ -596,7 +718,23 @@ private constructor( timeWindow: TimeWindow?, privacySalt: PrivacySalt, networkParameters: NetworkParameters - ) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList()) + ) : this( + inputs = inputs, + outputs = outputs, + commands = commands, + attachments = attachments, + id = id, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt, + networkParameters = networkParameters, + references = emptyList(), + componentGroups = null, + serializedInputs = null, + serializedReferences = null, + isAttachmentTrusted = { it.isUploaderTrusted() }, + verifierFactory = ::BasicVerifier + ) @Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.") fun copy(inputs: List>, @@ -618,7 +756,12 @@ private constructor( timeWindow = timeWindow, privacySalt = privacySalt, networkParameters = networkParameters, - references = references + references = references, + componentGroups = componentGroups, + serializedInputs = serializedInputs, + serializedReferences = serializedReferences, + isAttachmentTrusted = isAttachmentTrusted, + verifierFactory = verifierFactory ) } @@ -643,7 +786,12 @@ private constructor( timeWindow = timeWindow, privacySalt = privacySalt, networkParameters = networkParameters, - references = references + references = references, + componentGroups = componentGroups, + serializedInputs = serializedInputs, + serializedReferences = serializedReferences, + isAttachmentTrusted = isAttachmentTrusted, + verifierFactory = verifierFactory ) } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index c31011e51f..8b0cec6e58 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -70,8 +70,8 @@ open class TransactionBuilder( private fun defaultLockId() = (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID() private val log = contextLogger() - private val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" - private val FQCP = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+") + private const val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" + private val FQCP: Pattern = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+") private fun isValidJavaClass(identifier: String) = FQCP.matcher(identifier).matches() } diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 123d248ffb..89e81f39be 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -99,7 +99,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr @Throws(AttachmentResolutionException::class, TransactionResolutionException::class) @DeleteForDJVM fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction { - return toLedgerTransactionInternal( + return services.specialise( + toLedgerTransactionInternal( resolveIdentity = { services.identityService.partyFromKey(it) }, resolveAttachment = { services.attachments.openAttachment(it) }, resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) }, @@ -109,6 +110,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr }, // `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal] isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true } + ) ) } @@ -129,7 +131,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr * @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment]. * @throws TransactionResolutionException if an input was not found not using [resolveStateRef]. */ - @Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead") + @Deprecated("Use toLedgerTransaction(ServicesForResolution) instead") @Throws(AttachmentResolutionException::class, TransactionResolutionException::class) fun toLedgerTransaction( resolveIdentity: (PublicKey) -> Party?, diff --git a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt b/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt index e0545423c5..148aaff7bf 100644 --- a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt +++ b/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt @@ -1,7 +1,6 @@ package net.corda.core.internal import net.corda.core.contracts.* -import net.corda.core.crypto.PartialMerkleTree import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.node.NetworkParameters @@ -37,5 +36,3 @@ fun createLedgerTransaction( fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause) fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause) - -fun PartialMerkleTree.accessLeafIndex(id: SecureHash) = this.leafIndex(id) \ No newline at end of file diff --git a/node/build.gradle b/node/build.gradle index fc9b6b9238..63db4fade4 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -31,6 +31,13 @@ apply plugin: 'com.jfrog.artifactory' description 'Corda node modules' +repositories { + // Extra repository for the deterministic-rt JAR. + maven { + url "$artifactory_contextUrl/corda-dev" + } +} + //noinspection GroovyAssignabilityCheck configurations { integrationTestCompile.extendsFrom testCompile @@ -38,6 +45,11 @@ configurations { slowIntegrationTestCompile.extendsFrom testCompile slowIntegrationTestRuntimeOnly.extendsFrom testRuntimeOnly + + jdkRt.resolutionStrategy { + cacheChangingModulesFor 0, 'seconds' + } + deterministic } sourceSets { @@ -156,6 +168,19 @@ dependencies { // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:$typesafe_config_version" + // Sandbox for deterministic contract verification + compile "net.corda.djvm:corda-djvm:$djvm_version" + compile project(':serialization-djvm') + compile(project(':node:djvm')) { + transitive = false + } + jdkRt "net.corda:deterministic-rt:latest.integration" + deterministic project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + deterministic project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') + deterministic project(':serialization-djvm:deserializers') + deterministic project(':node:djvm') + deterministic "org.slf4j:slf4j-nop:$slf4j_version" + testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" testImplementation "junit:junit:$junit_version" @@ -245,8 +270,11 @@ tasks.withType(JavaCompile) { } tasks.withType(Test) { - if (JavaVersion.current() == JavaVersion.VERSION_11) + if (JavaVersion.current() == JavaVersion.VERSION_11) { jvmArgs '-Djdk.attach.allowAttachSelf=true' + } + systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath + systemProperty 'deterministic-sources.path', configurations.deterministic.asPath } task integrationTest(type: Test) { @@ -263,6 +291,9 @@ task slowIntegrationTest(type: Test) { // quasar exclusions upon agent code instrumentation at run-time quasar { + excludeClassLoaders.addAll( + 'net.corda.djvm.**' + ) excludePackages.addAll( "antlr**", "com.codahale**", @@ -271,10 +302,12 @@ quasar { "com.google.**", "com.lmax.**", "com.zaxxer.**", + "djvm**", "net.bytebuddy**", "io.github.classgraph**", "io.netty*", "liquibase**", + "net.corda.djvm**", "net.i2p.crypto.**", "nonapi.io.github.classgraph.**", "org.apiguardian.**", @@ -292,6 +325,10 @@ quasar { jar { baseName 'corda-node' + manifest { + attributes('Corda-Deterministic-Runtime': configurations.jdkRt.singleFile.name) + attributes('Corda-Deterministic-Classpath': configurations.deterministic.collect { it.name }.join(' ')) + } } publish { @@ -301,4 +338,4 @@ publish { test { maxHeapSize = "3g" maxParallelForks = (System.env.CORDA_NODE_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_NODE_TESTING_FORKS".toInteger() -} \ No newline at end of file +} diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index f11a7ee21e..6d0ff63cdb 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -8,6 +8,8 @@ apply plugin: 'com.jfrog.artifactory' description 'Corda standalone node' +evaluationDependsOn(':node') + configurations { runtimeArtifacts.extendsFrom runtimeClasspath capsuleRuntime @@ -34,28 +36,64 @@ capsule { version capsule_version } -task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) { +def nodeProject = project(':node') + +task buildCordaJAR(type: FatCapsule, dependsOn: [ + nodeProject.tasks.jar, + project(':core-deterministic').tasks.assemble, + project(':serialization-deterministic').tasks.assemble + ]) { applicationClass 'net.corda.node.Corda' archiveBaseName = 'corda' archiveVersion = corda_release_version archiveClassifier = jdkClassifier archiveName = archiveFileName.get() applicationSource = files( - project(':node').configurations.runtimeClasspath, - project(':node').tasks.jar, - project(':node').buildDir.toString() + '/resources/main/reference.conf', + nodeProject.configurations.runtimeClasspath, + nodeProject.tasks.jar, + nodeProject.buildDir.toString() + '/resources/main/reference.conf', "$rootDir/config/dev/log4j2.xml", 'NOTICE' // Copy CDDL notice ) from configurations.capsuleRuntime.files.collect { zipTree(it) } with jar + // The DJVM will share most of its dependencies with the node, but any extra ones that it needs + // are listed in the node's "deterministic" configuration and copied into a djvm subdirectory. + // + // Gradle may not resolve exactly the same transitive dependencies for both the runtimeClasspath + // and deterministic configurations - specifically, the artifacts' version numbers may differ slightly. + // And so we map the files by the resolved ModuleIdentifier objects instead, which just contain an + // artifact's group and name. + def cordaResolved = nodeProject.configurations['runtimeClasspath'].resolvedConfiguration.resolvedArtifacts.collectEntries { + [ (it.moduleVersion.id.module):it.file ] + } + def deterministicResolved = nodeProject.configurations['deterministic'].resolvedConfiguration.resolvedArtifacts.collectEntries { + [ (it.moduleVersion.id.module):it.file ] + } + def resolvedDifferences = deterministicResolved.keySet() - cordaResolved.keySet() + + cordaResolved.keySet().retainAll(deterministicResolved.keySet() - resolvedDifferences) + deterministicResolved.keySet().retainAll(resolvedDifferences) + + manifest { + // These are the dependencies that the deterministic Corda libraries share with Corda. + attributes('Corda-DJVM-Dependencies': cordaResolved.values().collect { it.name }.join(' ')) + } + + into('djvm') { + from nodeProject.configurations['jdkRt'].singleFile + from deterministicResolved.values() + fileMode = 0444 + } + capsuleManifest { applicationVersion = corda_release_version applicationId = "net.corda.node.Corda" // See experimental/quasar-hook/README.md for how to generate. - def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)" - javaAgents = quasar_classifier == null ? ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}"] : ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}"] + def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)" + def quasarClassLoaderExclusion = "l(net.corda.djvm.**)" + javaAgents = quasar_classifier == null ? ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] systemProperties['visualvm.display.name'] = 'Corda' if (JavaVersion.current() == JavaVersion.VERSION_1_8) { minJavaVersion = '1.8.0' @@ -70,8 +108,9 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) { // // If you change these flags, please also update Driver.kt jvmArgs = ['-Xmx512m', '-XX:+UseG1GC'] - if (JavaVersion.current() == JavaVersion.VERSION_11) + if (JavaVersion.current() == JavaVersion.VERSION_11) { jvmArgs += ['-Djdk.attach.allowAttachSelf=true'] + } } } diff --git a/node/capsule/src/main/java/CordaCaplet.java b/node/capsule/src/main/java/CordaCaplet.java index faeb62f4da..b366b3aef9 100644 --- a/node/capsule/src/main/java/CordaCaplet.java +++ b/node/capsule/src/main/java/CordaCaplet.java @@ -6,12 +6,22 @@ import com.typesafe.config.*; import sun.misc.Signal; import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; import java.util.stream.Stream; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.util.stream.Collectors.toMap; + public class CordaCaplet extends Capsule { + private static final String DJVM_DIR ="djvm"; private Config nodeConfig = null; private String baseDir = null; @@ -79,10 +89,76 @@ public class CordaCaplet extends Capsule { return null; } + private void installDJVM() { + Path djvmDir = Paths.get(baseDir, DJVM_DIR); + if (!djvmDir.toFile().mkdir() && !Files.isDirectory(djvmDir)) { + log(LOG_VERBOSE, "DJVM directory could not be created"); + } else { + try { + Path sourceDir = appDir().resolve(DJVM_DIR); + if (Files.isDirectory(sourceDir)) { + installCordaDependenciesForDJVM(sourceDir, djvmDir); + installTransitiveDependenciesForDJVM(appDir(), djvmDir); + } + } catch (IOException e) { + log(LOG_VERBOSE, "Failed to populate directory " + djvmDir.toAbsolutePath()); + log(LOG_VERBOSE, e); + } + } + } + + private void installCordaDependenciesForDJVM(Path sourceDir, Path targetDir) throws IOException { + try (DirectoryStream directory = Files.newDirectoryStream(sourceDir, file -> Files.isRegularFile(file))) { + for (Path sourceFile : directory) { + Path targetFile = targetDir.resolve(sourceFile.getFileName()); + installFile(sourceFile, targetFile); + } + } + } + + private void installTransitiveDependenciesForDJVM(Path sourceDir, Path targetDir) throws IOException { + Manifest manifest = getManifest(); + String[] transitives = manifest.getMainAttributes().getValue("Corda-DJVM-Dependencies").split("\\s++", 0); + for (String transitive : transitives) { + Path source = sourceDir.resolve(transitive); + if (Files.isRegularFile(source)) { + installFile(source, targetDir.resolve(transitive)); + } + } + } + + private Manifest getManifest() throws IOException { + URL capsule = getClass().getProtectionDomain().getCodeSource().getLocation(); + try (JarInputStream jar = new JarInputStream(capsule.openStream())) { + return jar.getManifest(); + } + } + + private void installFile(Path source, Path target) { + try { + // Forcibly reinstall this dependency. + Files.deleteIfExists(target); + Files.createSymbolicLink(target, source); + } catch (UnsupportedOperationException | IOException e) { + copyFile(source, target); + } + } + + private void copyFile(Path source, Path target) { + try { + Files.copy(source, target, REPLACE_EXISTING); + } catch (IOException e) { + //noinspection ResultOfMethodCallIgnored + target.toFile().delete(); + log(LOG_VERBOSE, e); + } + } + @Override protected ProcessBuilder prelaunch(List jvmArgs, List args) { checkJavaVersion(); nodeConfig = parseConfigFile(args); + installDJVM(); return super.prelaunch(jvmArgs, args); } @@ -155,10 +231,13 @@ public class CordaCaplet extends Capsule { // Add system properties, if specified, from the config. Map systemProps = new LinkedHashMap<>((Map) super.attribute(attr)); try { - Config overrideSystemProps = nodeConfig.getConfig("systemProperties"); + Map overrideSystemProps = nodeConfig.getConfig("systemProperties").entrySet().stream() + .map(Property::create) + .filter(Property::isValid) + .collect(toMap(Property::getKey, Property::getValue)); log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps); - for (Map.Entry entry : overrideSystemProps.entrySet()) { - systemProps.put(entry.getKey(), entry.getValue().unwrapped().toString()); + for (Map.Entry entry : overrideSystemProps.entrySet()) { + systemProps.put(entry.getKey(), entry.getValue().toString()); } } catch (ConfigException.Missing e) { // Ignore since it's ok to be Missing. Other errors would be unexpected. @@ -237,4 +316,33 @@ public class CordaCaplet extends Capsule { private Boolean isJAR(File file) { return file.getName().toLowerCase().endsWith(".jar"); } + + /** + * Helper class so that we can parse the "systemProperties" element of node.conf. + */ + private static class Property { + private final List path; + private final Object value; + + Property(List path, Object value) { + this.path = path; + this.value = value; + } + + boolean isValid() { + return path.size() == 1; + } + + String getKey() { + return path.get(0); + } + + Object getValue() { + return value; + } + + static Property create(Map.Entry entry) { + return new Property(ConfigUtil.splitPath(entry.getKey()), entry.getValue().unwrapped()); + } + } } diff --git a/node/djvm/build.gradle b/node/djvm/build.gradle new file mode 100644 index 0000000000..efd62b2363 --- /dev/null +++ b/node/djvm/build.gradle @@ -0,0 +1,24 @@ +apply from: '../../deterministic.gradle' +apply plugin: 'com.jfrog.artifactory' +apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'java-library' + +description 'Internal Corda Node modules for deterministic contract verification.' + +dependencies { + api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + api project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + api project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') +} + +jar { + archiveBaseName = 'corda-node-djvm' + manifest { + attributes('Automatic-Module-Name': 'net.corda.node.djvm') + attributes('Sealed': true) + } +} + +publish { + name jar.archiveBaseName.get() +} diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt new file mode 100644 index 0000000000..d846ad08c9 --- /dev/null +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt @@ -0,0 +1,62 @@ +@file:JvmName("AttachmentConstants") +package net.corda.node.djvm + +import net.corda.core.contracts.Attachment +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.Party +import java.io.InputStream +import java.security.PublicKey +import java.util.Collections.unmodifiableList +import java.util.function.Function + +private const val SIGNER_KEYS_IDX = 0 +private const val SIZE_IDX = 1 +private const val ID_IDX = 2 +private const val ATTACHMENT_IDX = 3 +private const val STREAMER_IDX = 4 + +class AttachmentBuilder : Function?, List?> { + private val attachments = mutableListOf() + + private fun unmodifiable(list: List): List { + return if (list.isEmpty()) { + emptyList() + } else { + unmodifiableList(list) + } + } + + override fun apply(inputs: Array?): List? { + return if (inputs == null) { + unmodifiable(attachments) + } else { + @Suppress("unchecked_cast") + attachments.add(SandboxAttachment( + signerKeys = inputs[SIGNER_KEYS_IDX] as List, + size = inputs[SIZE_IDX] as Int, + id = inputs[ID_IDX] as SecureHash, + attachment = inputs[ATTACHMENT_IDX], + streamer = inputs[STREAMER_IDX] as Function + )) + null + } + } +} + +/** + * This represents an [Attachment] from within the sandbox. + */ +class SandboxAttachment( + override val signerKeys: List, + override val size: Int, + override val id: SecureHash, + private val attachment: Any, + private val streamer: Function +) : Attachment { + @Suppress("OverridingDeprecatedMember") + override val signers: List = emptyList() + + override fun open(): InputStream { + return streamer.apply(attachment) + } +} diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/CommandBuilder.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/CommandBuilder.kt new file mode 100644 index 0000000000..311f8e69ff --- /dev/null +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/CommandBuilder.kt @@ -0,0 +1,40 @@ +package net.corda.node.djvm + +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.CommandWithParties +import net.corda.core.internal.lazyMapped +import java.security.PublicKey +import java.util.function.Function + +class CommandBuilder : Function, List>> { + @Suppress("unchecked_cast") + override fun apply(inputs: Array): List> { + val signers = inputs[0] as? List> ?: emptyList() + val commandsData = inputs[1] as? List ?: emptyList() + val partialMerkleLeafIndices = inputs[2] as? IntArray + + /** + * This logic has been lovingly reproduced from [net.corda.core.internal.deserialiseCommands]. + */ + return if (partialMerkleLeafIndices != null) { + check(commandsData.size <= signers.size) { + "Invalid Transaction. Fewer Signers (${signers.size}) than CommandData (${commandsData.size}) objects" + } + if (partialMerkleLeafIndices.isNotEmpty()) { + check(partialMerkleLeafIndices.max()!! < signers.size) { + "Invalid Transaction. A command with no corresponding signer detected" + } + } + commandsData.lazyMapped { commandData, index -> + CommandWithParties(signers[partialMerkleLeafIndices[index]], emptyList(), commandData) + } + } else { + check(commandsData.size == signers.size) { + "Invalid Transaction. Sizes of CommandData (${commandsData.size}) and Signers (${signers.size}) do not match" + } + commandsData.lazyMapped { commandData, index -> + CommandWithParties(signers[index], emptyList(), commandData) + } + } + } +} diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/ComponentBuilder.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/ComponentBuilder.kt new file mode 100644 index 0000000000..78c3efc737 --- /dev/null +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/ComponentBuilder.kt @@ -0,0 +1,24 @@ +package net.corda.node.djvm + +import net.corda.core.contracts.ComponentGroupEnum +import net.corda.core.internal.TransactionDeserialisationException +import net.corda.core.internal.lazyMapped +import net.corda.core.utilities.OpaqueBytes +import java.util.function.Function + +class ComponentBuilder : Function, List<*>> { + @Suppress("unchecked_cast", "TooGenericExceptionCaught") + override fun apply(inputs: Array): List<*> { + val deserializer = inputs[0] as Function + val groupType = inputs[1] as ComponentGroupEnum + val components = (inputs[2] as Array).map(::OpaqueBytes) + + return components.lazyMapped { component, index -> + try { + deserializer.apply(component.bytes) + } catch (e: Exception) { + throw TransactionDeserialisationException(groupType, index, e) + } + } + } +} diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt new file mode 100644 index 0000000000..dc3affd9e2 --- /dev/null +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt @@ -0,0 +1,51 @@ +@file:JvmName("LtxConstants") +package net.corda.node.djvm + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.CommandWithParties +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.PrivacySalt +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.contracts.TransactionState +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.Party +import net.corda.core.node.NetworkParameters +import net.corda.core.transactions.LedgerTransaction +import java.util.function.Function + +private const val TX_INPUTS = 0 +private const val TX_OUTPUTS = 1 +private const val TX_COMMANDS = 2 +private const val TX_ATTACHMENTS = 3 +private const val TX_ID = 4 +private const val TX_NOTARY = 5 +private const val TX_TIME_WINDOW = 6 +private const val TX_PRIVACY_SALT = 7 +private const val TX_NETWORK_PARAMETERS = 8 +private const val TX_REFERENCES = 9 + +class LtxFactory : Function, LedgerTransaction> { + + @Suppress("unchecked_cast") + override fun apply(txArgs: Array): LedgerTransaction { + return LedgerTransaction.createForSandbox( + inputs = (txArgs[TX_INPUTS] as Array>).map { it.toStateAndRef() }, + outputs = (txArgs[TX_OUTPUTS] as? List>) ?: emptyList(), + commands = (txArgs[TX_COMMANDS] as? List>) ?: emptyList(), + attachments = (txArgs[TX_ATTACHMENTS] as? List) ?: emptyList(), + id = txArgs[TX_ID] as SecureHash, + notary = txArgs[TX_NOTARY] as? Party, + timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow, + privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt, + networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters, + references = (txArgs[TX_REFERENCES] as Array>).map { it.toStateAndRef() } + ) + } + + private fun Array<*>.toStateAndRef(): StateAndRef { + return StateAndRef(this[0] as TransactionState<*>, this[1] as StateRef) + } +} diff --git a/node/djvm/src/main/resources/META-INF/DJVM-preload b/node/djvm/src/main/resources/META-INF/DJVM-preload new file mode 100644 index 0000000000..e69de29bb2 diff --git a/node/src/integration-test/kotlin/net/corda/contracts/djvm/attachment/SandboxAttachmentContract.kt b/node/src/integration-test/kotlin/net/corda/contracts/djvm/attachment/SandboxAttachmentContract.kt new file mode 100644 index 0000000000..d669f1f9ce --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/contracts/djvm/attachment/SandboxAttachmentContract.kt @@ -0,0 +1,38 @@ +package net.corda.contracts.djvm.attachment + +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction +import java.io.ByteArrayOutputStream + +class SandboxAttachmentContract : Contract { + override fun verify(tx: LedgerTransaction) { + val attachments = tx.attachments + require(attachments.isNotEmpty()) { "Attachments are missing for TX=${tx.id}" } + + require(attachments.size == 1) { "Did not expect to find ${attachments.size} attachments for TX=${tx.id}" } + val attachment = attachments[0] + require(attachment.size > 0) { "Attachment ${attachment.id} has no contents for TX=${tx.id}" } + + val keyCount = attachment.signerKeys.size + require(keyCount == 1) { "Did not expect to find $keyCount signing keys for attachment ${attachment.id}, TX=${tx.id}" } + + tx.commandsOfType(ExtractFile::class.java).forEach { extract -> + val fileName = extract.value.fileName + val contents = ByteArrayOutputStream().use { + attachment.extractFile(fileName, it) + it + }.toByteArray() + require(contents.isNotEmpty()) { "File $fileName has no contents for TX=${tx.id}" } + } + } + + @Suppress("CanBeParameter", "MemberVisibilityCanBePrivate") + class State(val issuer: AbstractParty) : ContractState { + override val participants: List = listOf(issuer) + } + + class ExtractFile(val fileName: String) : CommandData +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/contracts/djvm/broken/NonDeterministicContract.kt b/node/src/integration-test/kotlin/net/corda/contracts/djvm/broken/NonDeterministicContract.kt new file mode 100644 index 0000000000..a74928b174 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/contracts/djvm/broken/NonDeterministicContract.kt @@ -0,0 +1,50 @@ +package net.corda.contracts.djvm.broken + +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.TypeOnlyCommandData +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction +import java.time.Instant +import java.util.* + +class NonDeterministicContract : Contract { + override fun verify(tx: LedgerTransaction) { + when { + tx.commandsOfType(InstantNow::class.java).isNotEmpty() -> verifyInstantNow() + tx.commandsOfType(CurrentTimeMillis::class.java).isNotEmpty() -> verifyCurrentTimeMillis() + tx.commandsOfType(NanoTime::class.java).isNotEmpty() -> verifyNanoTime() + tx.commandsOfType(RandomUUID::class.java).isNotEmpty() -> UUID.randomUUID() + tx.commandsOfType(WithReflection::class.java).isNotEmpty() -> verifyNoReflection() + else -> {} + } + } + + private fun verifyInstantNow() { + Instant.now() + } + + private fun verifyCurrentTimeMillis() { + System.currentTimeMillis() + } + + private fun verifyNanoTime() { + System.nanoTime() + } + + private fun verifyNoReflection() { + Date::class.java.getDeclaredConstructor().newInstance() + } + + @Suppress("CanBeParameter", "MemberVisibilityCanBePrivate") + class State(val issuer: AbstractParty) : ContractState { + override val participants: List = listOf(issuer) + } + + class InstantNow : TypeOnlyCommandData() + class CurrentTimeMillis : TypeOnlyCommandData() + class NanoTime : TypeOnlyCommandData() + class RandomUUID : TypeOnlyCommandData() + class WithReflection : TypeOnlyCommandData() + class NoOperation : TypeOnlyCommandData() +} diff --git a/node/src/integration-test/kotlin/net/corda/contracts/djvm/crypto/DeterministicCryptoContract.kt b/node/src/integration-test/kotlin/net/corda/contracts/djvm/crypto/DeterministicCryptoContract.kt new file mode 100644 index 0000000000..e86a204dcc --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/contracts/djvm/crypto/DeterministicCryptoContract.kt @@ -0,0 +1,39 @@ +package net.corda.contracts.djvm.crypto + +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.crypto.Crypto +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.OpaqueBytes +import java.security.PublicKey + +class DeterministicCryptoContract : Contract { + override fun verify(tx: LedgerTransaction) { + val cryptoData = tx.outputsOfType(CryptoState::class.java) + val validators = tx.commandsOfType(Validate::class.java) + + val isValid = validators.all { validate -> + with (validate.value) { + cryptoData.all { crypto -> + Crypto.doVerify(schemeCodeName, publicKey, crypto.signature.bytes, crypto.original.bytes) + } + } + } + + require(cryptoData.isNotEmpty() && validators.isNotEmpty() && isValid) { + "Failed to validate signatures in command data" + } + } + + @Suppress("CanBeParameter", "MemberVisibilityCanBePrivate") + class CryptoState(val owner: AbstractParty, val original: OpaqueBytes, val signature: OpaqueBytes) : ContractState { + override val participants: List = listOf(owner) + } + + class Validate( + val schemeCodeName: String, + val publicKey: PublicKey + ) : CommandData +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/flows/djvm/attachment/SandboxAttachmentFlow.kt b/node/src/integration-test/kotlin/net/corda/flows/djvm/attachment/SandboxAttachmentFlow.kt new file mode 100644 index 0000000000..e51313602e --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/flows/djvm/attachment/SandboxAttachmentFlow.kt @@ -0,0 +1,27 @@ +package net.corda.flows.djvm.attachment + +import co.paralleluniverse.fibers.Suspendable +import net.corda.contracts.djvm.attachment.SandboxAttachmentContract +import net.corda.core.contracts.Command +import net.corda.core.contracts.CommandData +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByRPC +import net.corda.core.transactions.TransactionBuilder + +@InitiatingFlow +@StartableByRPC +class SandboxAttachmentFlow(private val command: CommandData) : FlowLogic() { + @Suspendable + override fun call(): SecureHash { + val notary = serviceHub.networkMapCache.notaryIdentities[0] + val stx = serviceHub.signInitialTransaction( + TransactionBuilder(notary) + .addOutputState(SandboxAttachmentContract.State(ourIdentity)) + .addCommand(Command(command, ourIdentity.owningKey)) + ) + stx.verify(serviceHub, checkSufficientSignatures = false) + return stx.id + } +} diff --git a/node/src/integration-test/kotlin/net/corda/flows/djvm/broken/NonDeterministicFlow.kt b/node/src/integration-test/kotlin/net/corda/flows/djvm/broken/NonDeterministicFlow.kt new file mode 100644 index 0000000000..ca9ddad1ef --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/flows/djvm/broken/NonDeterministicFlow.kt @@ -0,0 +1,27 @@ +package net.corda.flows.djvm.broken + +import co.paralleluniverse.fibers.Suspendable +import net.corda.contracts.djvm.broken.NonDeterministicContract +import net.corda.core.contracts.Command +import net.corda.core.contracts.TypeOnlyCommandData +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByRPC +import net.corda.core.transactions.TransactionBuilder + +@InitiatingFlow +@StartableByRPC +class NonDeterministicFlow(private val trouble: TypeOnlyCommandData) : FlowLogic() { + @Suspendable + override fun call(): SecureHash { + val notary = serviceHub.networkMapCache.notaryIdentities[0] + val stx = serviceHub.signInitialTransaction( + TransactionBuilder(notary) + .addOutputState(NonDeterministicContract.State(ourIdentity)) + .addCommand(Command(trouble, ourIdentity.owningKey)) + ) + stx.verify(serviceHub, checkSufficientSignatures = false) + return stx.id + } +} diff --git a/node/src/integration-test/kotlin/net/corda/flows/djvm/crypto/DeterministicCryptoFlow.kt b/node/src/integration-test/kotlin/net/corda/flows/djvm/crypto/DeterministicCryptoFlow.kt new file mode 100644 index 0000000000..18fb73c380 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/flows/djvm/crypto/DeterministicCryptoFlow.kt @@ -0,0 +1,32 @@ +package net.corda.flows.djvm.crypto + +import co.paralleluniverse.fibers.Suspendable +import net.corda.contracts.djvm.crypto.DeterministicCryptoContract +import net.corda.core.contracts.Command +import net.corda.core.contracts.CommandData +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByRPC +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes + +@InitiatingFlow +@StartableByRPC +class DeterministicCryptoFlow( + private val command: CommandData, + private val original: OpaqueBytes, + private val signature: OpaqueBytes +) : FlowLogic() { + @Suspendable + override fun call(): SecureHash { + val notary = serviceHub.networkMapCache.notaryIdentities[0] + val stx = serviceHub.signInitialTransaction( + TransactionBuilder(notary) + .addOutputState(DeterministicCryptoContract.CryptoState(ourIdentity, original, signature)) + .addCommand(Command(command, ourIdentity.owningKey)) + ) + stx.verify(serviceHub, checkSufficientSignatures = false) + return stx.id + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/DeterministicSourcesRule.kt b/node/src/integration-test/kotlin/net/corda/node/DeterministicSourcesRule.kt new file mode 100644 index 0000000000..4171f40fda --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/DeterministicSourcesRule.kt @@ -0,0 +1,31 @@ +package net.corda.node + +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.test.fail + +class DeterministicSourcesRule : TestRule { + private var deterministicRt: Path? = null + private var deterministicSources: List? = null + + val bootstrap: Path get() = deterministicRt ?: fail("deterministic-rt.path property not set") + val corda: List get() = deterministicSources ?: fail("deterministic-sources.path property not set") + + override fun apply(statement: Statement, description: Description?): Statement { + deterministicRt = System.getProperty("deterministic-rt.path")?.run { Paths.get(this) } + deterministicSources = System.getProperty("deterministic-sources.path")?.split(File.pathSeparator) + ?.map { Paths.get(it) } + ?.filter { Files.exists(it) } + + return object : Statement() { + override fun evaluate() { + statement.evaluate() + } + } + } +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt new file mode 100644 index 0000000000..d3ba5096cb --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt @@ -0,0 +1,73 @@ +package net.corda.node.services + +import net.corda.contracts.djvm.crypto.DeterministicCryptoContract.Validate +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.Crypto.DEFAULT_SIGNATURE_SCHEME +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor +import net.corda.flows.djvm.crypto.DeterministicCryptoFlow +import net.corda.node.DeterministicSourcesRule +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.incrementalPortAllocation +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.CustomCordapp +import net.corda.testing.node.internal.cordappWithPackages +import org.junit.ClassRule +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow +import java.security.KeyPairGenerator + +class DeterministicContractCryptoTest { + companion object { + const val MESSAGE = "Very Important Data! Do Not Change!" + val logger = loggerFor() + + @ClassRule + @JvmField + val djvmSources = DeterministicSourcesRule() + + fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters { + return DriverParameters( + portAllocation = incrementalPortAllocation(), + startNodesInProcess = false, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), + cordappsForAllNodes = listOf( + cordappWithPackages("net.corda.flows.djvm.crypto"), + CustomCordapp( + packages = setOf("net.corda.contracts.djvm.crypto"), + name = "deterministic-crypto-contract", + signingInfo = CustomCordapp.SigningInfo() + ) + ), + djvmBootstrapSource = djvmSources.bootstrap, + djvmCordaSource = djvmSources.corda + ) + } + } + + @Test + fun `test DJVM can verify using crypto`() { + val keyPair = KeyPairGenerator.getInstance(DEFAULT_SIGNATURE_SCHEME.algorithmName).genKeyPair() + val importantData = OpaqueBytes(MESSAGE.toByteArray()) + val signature = OpaqueBytes(Crypto.doSign(DEFAULT_SIGNATURE_SCHEME, keyPair.`private`, importantData.bytes)) + + val validate = Validate( + schemeCodeName = DEFAULT_SIGNATURE_SCHEME.schemeCodeName, + publicKey = keyPair.`public` + ) + + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val txId = assertDoesNotThrow { + alice.rpc.startFlow(::DeterministicCryptoFlow, validate, importantData, signature) + .returnValue.getOrThrow() + } + logger.info("TX-ID: {}", txId) + } + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt new file mode 100644 index 0000000000..0625ae76cc --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt @@ -0,0 +1,133 @@ +package net.corda.node.services + +import net.corda.contracts.djvm.broken.NonDeterministicContract.CurrentTimeMillis +import net.corda.contracts.djvm.broken.NonDeterministicContract.InstantNow +import net.corda.contracts.djvm.broken.NonDeterministicContract.NanoTime +import net.corda.contracts.djvm.broken.NonDeterministicContract.NoOperation +import net.corda.contracts.djvm.broken.NonDeterministicContract.RandomUUID +import net.corda.contracts.djvm.broken.NonDeterministicContract.WithReflection +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor +import net.corda.flows.djvm.broken.NonDeterministicFlow +import net.corda.node.DeterministicSourcesRule +import net.corda.node.internal.djvm.DeterministicVerificationException +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.incrementalPortAllocation +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.CustomCordapp +import net.corda.testing.node.internal.cordappWithPackages +import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat +import org.junit.ClassRule +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows + +class NonDeterministicContractVerifyTest { + companion object { + val logger = loggerFor() + + @ClassRule + @JvmField + val djvmSources = DeterministicSourcesRule() + + fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters { + return DriverParameters( + portAllocation = incrementalPortAllocation(), + startNodesInProcess =false, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), + cordappsForAllNodes = listOf( + cordappWithPackages("net.corda.flows.djvm.broken"), + CustomCordapp( + packages = setOf("net.corda.contracts.djvm.broken"), + name = "nondeterministic-contract", + signingInfo = CustomCordapp.SigningInfo() + ) + ), + djvmBootstrapSource = djvmSources.bootstrap, + djvmCordaSource = djvmSources.corda + ) + } + } + + @Test + fun `test DJVM rejects contract that uses Instant now`() { + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val ex = assertThrows { + alice.rpc.startFlow(::NonDeterministicFlow, InstantNow()) + .returnValue.getOrThrow() + } + assertThat(ex) + .hasMessageStartingWith("NoSuchMethodError: sandbox.java.time.Instant.now()Lsandbox/java/time/Instant;, ") + } + } + + @Test + fun `test DJVM rejects contract that uses System currentTimeMillis`() { + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val ex = assertThrows { + alice.rpc.startFlow(::NonDeterministicFlow, CurrentTimeMillis()) + .returnValue.getOrThrow() + } + assertThat(ex) + .hasMessageStartingWith("NoSuchMethodError: sandbox.java.lang.System.currentTimeMillis()J, ") + } + } + + @Test + fun `test DJVM rejects contract that uses System nanoTime`() { + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val ex = assertThrows { + alice.rpc.startFlow(::NonDeterministicFlow, NanoTime()) + .returnValue.getOrThrow() + } + assertThat(ex) + .hasMessageStartingWith("NoSuchMethodError: sandbox.java.lang.System.nanoTime()J, ") + } + } + + @Test + fun `test DJVM rejects contract that uses UUID randomUUID`() { + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val ex = assertThrows { + alice.rpc.startFlow(::NonDeterministicFlow, RandomUUID()) + .returnValue.getOrThrow() + } + assertThat(ex) + .hasMessageStartingWith("NoSuchMethodError: sandbox.java.util.UUID.randomUUID()Lsandbox/java/util/UUID;, ") + } + } + + @Test + fun `test DJVM rejects contract that uses reflection`() { + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val ex = assertThrows { + alice.rpc.startFlow(::NonDeterministicFlow, WithReflection()) + .returnValue.getOrThrow() + } + assertThat(ex).hasMessageStartingWith( + "RuleViolationError: Disallowed reference to API; java.lang.Class.getDeclaredConstructor(Class[]), " + ) + } + } + + @Test + fun `test DJVM can succeed`() { + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val txId = assertDoesNotThrow { + alice.rpc.startFlow(::NonDeterministicFlow, NoOperation()) + .returnValue.getOrThrow() + } + logger.info("TX-ID: {}", txId) + } + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt new file mode 100644 index 0000000000..d633f9e1ce --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt @@ -0,0 +1,81 @@ +package net.corda.node.services + +import net.corda.contracts.djvm.attachment.SandboxAttachmentContract +import net.corda.contracts.djvm.attachment.SandboxAttachmentContract.ExtractFile +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor +import net.corda.djvm.code.asResourcePath +import net.corda.flows.djvm.attachment.SandboxAttachmentFlow +import net.corda.node.DeterministicSourcesRule +import net.corda.node.internal.djvm.DeterministicVerificationException +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.incrementalPortAllocation +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.CustomCordapp +import net.corda.testing.node.internal.cordappWithPackages +import org.assertj.core.api.Assertions.assertThat +import org.junit.ClassRule +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows + +class SandboxAttachmentsTest { + companion object { + val logger = loggerFor() + + @ClassRule + @JvmField + val djvmSources = DeterministicSourcesRule() + + fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters { + return DriverParameters( + portAllocation = incrementalPortAllocation(), + startNodesInProcess = false, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), + cordappsForAllNodes = listOf( + cordappWithPackages("net.corda.flows.djvm.attachment"), + CustomCordapp( + packages = setOf("net.corda.contracts.djvm.attachment"), + name = "sandbox-attachment-contract", + signingInfo = CustomCordapp.SigningInfo() + ) + ), + djvmBootstrapSource = djvmSources.bootstrap, + djvmCordaSource = djvmSources.corda + ) + } + } + + @Test + fun `test attachment accessible within sandbox`() { + val extractFile = ExtractFile(SandboxAttachmentContract::class.java.name.asResourcePath + ".class") + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val txId = assertDoesNotThrow { + alice.rpc.startFlow(::SandboxAttachmentFlow, extractFile) + .returnValue.getOrThrow() + } + logger.info("TX-ID: {}", txId) + } + } + + @Test + fun `test attachment file not found within sandbox`() { + val extractFile = ExtractFile("does/not/Exist.class") + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val ex = assertThrows { + alice.rpc.startFlow(::SandboxAttachmentFlow, extractFile) + .returnValue.getOrThrow() + } + assertThat(ex) + .hasMessageStartingWith("sandbox.net.corda.core.contracts.TransactionVerificationException\$ContractRejection -> ") + .hasMessageContaining(" Contract verification failed: does/not/Exist.class, " ) + .hasMessageContaining(" contract: sandbox.net.corda.contracts.djvm.attachment.SandboxAttachmentContract, ") + } + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index a37a452c7f..8918ee1026 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -12,38 +12,84 @@ import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.internal.AliasPrivateKey import net.corda.core.crypto.newSecureRandom -import net.corda.core.flows.* +import net.corda.core.flows.ContractUpgradeFlow +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowLogicRefFactory +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.NotaryChangeFlow +import net.corda.core.flows.NotaryFlow +import net.corda.core.flows.StartableByService import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.* +import net.corda.core.internal.AttachmentTrustCalculator +import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.NODE_INFO_DIRECTORY +import net.corda.core.internal.NamedCacheFactory +import net.corda.core.internal.NetworkParametersStorage +import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.div import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.internal.notary.NotaryService -import net.corda.core.messaging.* -import net.corda.core.node.* -import net.corda.core.node.services.* +import net.corda.core.internal.rootMessage +import net.corda.core.internal.uncheckedCast +import net.corda.core.messaging.ClientRpcSslOptions +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.FlowHandle +import net.corda.core.messaging.FlowHandleImpl +import net.corda.core.messaging.FlowProgressHandle +import net.corda.core.messaging.FlowProgressHandleImpl +import net.corda.core.messaging.RPCOps +import net.corda.core.node.AppServiceHub +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NodeInfo +import net.corda.core.node.ServiceHub +import net.corda.core.node.ServicesForResolution +import net.corda.core.node.services.ContractUpgradeService +import net.corda.core.node.services.CordaService +import net.corda.core.node.services.IdentityService +import net.corda.core.node.services.KeyManagementService +import net.corda.core.node.services.TransactionVerifierService import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.minutes +import net.corda.djvm.source.ApiSource +import net.corda.djvm.source.EmptyApi +import net.corda.djvm.source.UserSource import net.corda.node.CordaClock import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation -import net.corda.node.internal.cordapp.* +import net.corda.node.internal.cordapp.CordappConfigFileProvider +import net.corda.node.internal.cordapp.CordappProviderImpl +import net.corda.node.internal.cordapp.CordappProviderInternal +import net.corda.node.internal.cordapp.JarScanningCordappLoader +import net.corda.node.internal.cordapp.VirtualCordapp import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler -import net.corda.node.services.api.* +import net.corda.node.services.api.AuditService +import net.corda.node.services.api.DummyAuditService +import net.corda.node.services.api.FlowStarter +import net.corda.node.services.api.MonitoringService +import net.corda.node.services.api.NetworkMapCacheInternal +import net.corda.node.services.api.NodePropertiesStore +import net.corda.node.services.api.SchemaService +import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.VaultServiceInternal +import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.attachments.NodeAttachmentTrustCalculator import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate @@ -61,15 +107,36 @@ import net.corda.node.services.network.NetworkMapClient import net.corda.node.services.network.NetworkMapUpdater import net.corda.node.services.network.NodeInfoWatcher import net.corda.node.services.network.PersistentNetworkMapCache -import net.corda.node.services.persistence.* +import net.corda.node.services.persistence.AbstractPartyDescriptor +import net.corda.node.services.persistence.AbstractPartyToX500NameAsStringConverter +import net.corda.node.services.persistence.AttachmentStorageInternal +import net.corda.node.services.persistence.DBCheckpointStorage +import net.corda.node.services.persistence.DBTransactionMappingStorage +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.node.services.persistence.NodePropertiesPersistentStore +import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl +import net.corda.node.services.persistence.PublicKeyToTextConverter import net.corda.node.services.rpc.CheckpointDumper import net.corda.node.services.schema.NodeSchemaService -import net.corda.node.services.statemachine.* +import net.corda.node.services.statemachine.ExternalEvent +import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl +import net.corda.node.services.statemachine.FlowMonitor +import net.corda.node.services.statemachine.SingleThreadedStateMachineManager +import net.corda.node.services.statemachine.StaffedFlowHospital +import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerInternal +import net.corda.node.services.transactions.BasicVerifierFactoryService +import net.corda.node.services.transactions.DeterministicVerifierFactoryService import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.SimpleNotaryService +import net.corda.node.services.transactions.VerifierFactoryService import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService -import net.corda.node.utilities.* +import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.BindableNamedCacheFactory +import net.corda.node.utilities.NamedThreadFactory +import net.corda.node.utilities.NotaryLoader import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.config.CertificateStore @@ -85,7 +152,12 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFI import net.corda.nodeapi.internal.cryptoservice.CryptoServiceFactory import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService -import net.corda.nodeapi.internal.persistence.* +import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException +import net.corda.nodeapi.internal.persistence.OutstandingDatabaseChangesException +import net.corda.nodeapi.internal.persistence.SchemaMigration import net.corda.tools.shell.InteractiveShell import org.apache.activemq.artemis.utils.ReusableLatch import org.jolokia.jvmagent.JolokiaServer @@ -103,13 +175,15 @@ import java.sql.Connection import java.time.Clock import java.time.Duration import java.time.format.DateTimeParseException -import java.util.* +import java.util.Objects +import java.util.Properties import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.SECONDS import java.util.function.Consumer import javax.persistence.EntityManager +import kotlin.collections.ArrayList /** * A base node implementation that can be customised either for production (with real implementations that do real @@ -125,7 +199,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val versionInfo: VersionInfo, protected val flowManager: FlowManager, val serverThread: AffinityExecutor.ServiceAffinityExecutor, - val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() { + val busyNodeLatch: ReusableLatch = ReusableLatch(), + djvmBootstrapSource: ApiSource = EmptyApi, + djvmCordaSource: UserSource? = null) : SingletonSerializeAsToken() { protected abstract val log: Logger @Suppress("LeakingThis") @@ -214,6 +290,18 @@ abstract class AbstractNode(val configuration: NodeConfiguration, ).closeOnStop() @Suppress("LeakingThis") val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize() + val verifierFactoryService: VerifierFactoryService = if (djvmCordaSource != null) { + DeterministicVerifierFactoryService(djvmBootstrapSource, djvmCordaSource).apply { + log.info("DJVM sandbox enabled for deterministic contract verification.") + if (!configuration.devMode) { + log.info("Generating Corda classes for DJVM sandbox.") + generateSandbox() + } + tokenize() + } + } else { + BasicVerifierFactoryService() + } val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize() val auditService = DummyAuditService().tokenize() @Suppress("LeakingThis") @@ -326,7 +414,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, open fun start(): S { check(started == null) { "Node has already been started" } - if (configuration.devMode) { + if (configuration.devMode && System.getProperty("co.paralleluniverse.fibers.verifyInstrumentation") == null) { System.setProperty("co.paralleluniverse.fibers.verifyInstrumentation", "true") } log.info("Node starting up ...") @@ -1069,6 +1157,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override fun registerUnloadHandler(runOnStop: () -> Unit) { this@AbstractNode.runOnStop += runOnStop } + + override fun specialise(ltx: LedgerTransaction): LedgerTransaction { + val ledgerTransaction = servicesForResolution.specialise(ltx) + return verifierFactoryService.apply(ledgerTransaction) + } } } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index fb27f35d4c..a6cd49e0fa 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -4,6 +4,7 @@ import com.codahale.metrics.MetricFilter import com.codahale.metrics.MetricRegistry import com.codahale.metrics.jmx.JmxReporter import com.github.benmanes.caffeine.cache.Caffeine +import com.jcabi.manifests.Manifests import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter import com.palominolabs.metrics.newrelic.NewRelicReporter import io.netty.util.NettyRuntime @@ -19,6 +20,7 @@ import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.div import net.corda.core.internal.errors.AddressBindingException import net.corda.core.internal.getJavaUpdateVersion +import net.corda.core.internal.isRegularFile import net.corda.core.internal.notary.NotaryService import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps @@ -29,6 +31,11 @@ import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger +import net.corda.djvm.source.ApiSource +import net.corda.djvm.source.BootstrapClassLoader +import net.corda.djvm.source.EmptyApi +import net.corda.djvm.source.UserPathSource +import net.corda.djvm.source.UserSource import net.corda.node.CordaClock import net.corda.node.SimpleClock import net.corda.node.VersionInfo @@ -44,7 +51,12 @@ import net.corda.node.services.Permissions import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.StartedNodeServices -import net.corda.node.services.config.* +import net.corda.node.services.config.JmxReporterType +import net.corda.node.services.config.MB +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.SecurityConfiguration +import net.corda.node.services.config.shouldInitCrashShell +import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.P2PMessagingClient @@ -52,7 +64,12 @@ import net.corda.node.services.rpc.ArtemisRpcBroker import net.corda.node.services.rpc.InternalRPCMessagingClient import net.corda.node.services.rpc.RPCServerConfiguration import net.corda.node.services.statemachine.StateMachineManager -import net.corda.node.utilities.* +import net.corda.node.utilities.AddressUtils +import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.BindableNamedCacheFactory +import net.corda.node.utilities.DefaultNamedCacheFactory +import net.corda.node.utilities.DemoClock +import net.corda.node.utilities.errorAndTerminate import net.corda.nodeapi.internal.ArtemisMessagingClient import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER @@ -62,7 +79,11 @@ import net.corda.nodeapi.internal.bridging.BridgeControlListener import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException -import net.corda.serialization.internal.* +import net.corda.serialization.internal.AMQP_P2P_CONTEXT +import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT +import net.corda.serialization.internal.AMQP_RPC_SERVER_CONTEXT +import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT +import net.corda.serialization.internal.SerializationFactoryImpl import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey import net.corda.serialization.internal.amqp.SerializerFactory import org.apache.commons.lang3.SystemUtils @@ -75,6 +96,7 @@ import java.lang.Long.max import java.lang.Long.min import java.net.BindException import java.net.InetAddress +import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.time.Clock @@ -99,7 +121,9 @@ open class Node(configuration: NodeConfiguration, versionInfo: VersionInfo, private val initialiseSerialization: Boolean = true, flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides), - cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory() + cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory(), + djvmBootstrapSource: ApiSource = createBootstrapSource(configuration), + djvmCordaSource: UserSource? = createCordaSource(configuration) ) : AbstractNode( configuration, createClock(configuration), @@ -107,13 +131,19 @@ open class Node(configuration: NodeConfiguration, versionInfo, flowManager, // Under normal (non-test execution) it will always be "1" - AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1) + AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1), + djvmBootstrapSource = djvmBootstrapSource, + djvmCordaSource = djvmCordaSource ) { override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): NodeInfo = nodeInfo companion object { + private const val CORDA_DETERMINISTIC_RUNTIME_ATTR = "Corda-Deterministic-Runtime" + private const val CORDA_DETERMINISTIC_CLASSPATH_ATTR = "Corda-Deterministic-Classpath" + private const val CORDA_DJVM = "net.corda.djvm" + private val staticLog = contextLogger() var renderBasicInfoToConsole = true @@ -172,6 +202,74 @@ open class Node(configuration: NodeConfiguration, false } } + + private fun manifestValue(attrName: String): String? = if (Manifests.exists(attrName)) Manifests.read(attrName) else null + + private fun createManifestCordaSource(config: NodeConfiguration): UserSource? { + val classpathSource = config.baseDirectory.resolve("djvm") + val djvmClasspath = manifestValue(CORDA_DETERMINISTIC_CLASSPATH_ATTR) + + return if (djvmClasspath == null) { + staticLog.warn("{} missing from MANIFEST.MF - deterministic contract verification now impossible!", + CORDA_DETERMINISTIC_CLASSPATH_ATTR) + null + } else if (!Files.isDirectory(classpathSource)) { + staticLog.warn("{} directory does not exist - deterministic contract verification now impossible!", + classpathSource.toAbsolutePath()) + null + } else { + val files = djvmClasspath.split("\\s++".toRegex(), 0).map { classpathSource.resolve(it) } + .filter { Files.isRegularFile(it) || Files.isSymbolicLink(it) } + staticLog.info("Corda Deterministic Libraries: {}", files.map(Path::getFileName).joinToString()) + + val jars = files.map { it.toUri().toURL() }.toTypedArray() + UserPathSource(jars) + } + } + + private fun createManifestBootstrapSource(config: NodeConfiguration): ApiSource { + val deterministicRt = manifestValue(CORDA_DETERMINISTIC_RUNTIME_ATTR) + if (deterministicRt == null) { + staticLog.warn("{} missing from MANIFEST.MF - will use host JVM for deterministic runtime.", + CORDA_DETERMINISTIC_RUNTIME_ATTR) + return EmptyApi + } + + val bootstrapSource = config.baseDirectory.resolve("djvm").resolve(deterministicRt) + return if (bootstrapSource.isRegularFile()) { + staticLog.info("Deterministic Runtime: {}", bootstrapSource.fileName) + BootstrapClassLoader(bootstrapSource) + } else { + staticLog.warn("NO DETERMINISTIC RUNTIME FOUND - will use host JVM instead.") + EmptyApi + } + } + + private fun createBootstrapSource(config: NodeConfiguration): ApiSource { + val djvm = config.devModeOptions?.djvm + return if (config.devMode && djvm != null) { + djvm.bootstrapSource?.let { BootstrapClassLoader(Paths.get(it)) } ?: EmptyApi + } else if (java.lang.Boolean.getBoolean(CORDA_DJVM)) { + createManifestBootstrapSource(config) + } else { + EmptyApi + } + } + + private fun createCordaSource(config: NodeConfiguration): UserSource? { + val djvm = config.devModeOptions?.djvm + return if (config.devMode && djvm != null) { + if (djvm.cordaSource.isEmpty()) { + null + } else { + UserPathSource(djvm.cordaSource.map { Paths.get(it) }) + } + } else if (java.lang.Boolean.getBoolean(CORDA_DJVM)) { + createManifestCordaSource(config) + } else { + null + } + } } override val log: Logger get() = staticLog diff --git a/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt b/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt index b8463ee5ec..f5836c0cc5 100644 --- a/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt @@ -35,7 +35,7 @@ data class ServicesForResolutionImpl( return stateRefs.groupBy { it.txhash }.flatMap { val stx = validatedTransactions.getTransaction(it.key) ?: throw TransactionResolutionException(it.key) val baseTx = stx.resolveBaseTransaction(this) - it.value.map { StateAndRef(baseTx.outputs[it.index], it) } + it.value.map { ref -> StateAndRef(baseTx.outputs[ref.index], ref) } }.toSet() } diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt new file mode 100644 index 0000000000..a16447dced --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt @@ -0,0 +1,32 @@ +package net.corda.node.internal.djvm + +import net.corda.core.contracts.Attachment +import net.corda.core.serialization.serialize +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.node.djvm.AttachmentBuilder +import java.util.function.Function + +class AttachmentFactory( + private val classLoader: SandboxClassLoader, + private val taskFactory: Function>, + private val sandboxBasicInput: Function, + private val serializer: Serializer +) { + private val sandboxOpenAttachment: Function = classLoader.createForImport( + Function(Attachment::open).andThen(sandboxBasicInput) + ) + + fun toSandbox(attachments: List): Any? { + val builder = classLoader.createTaskFor(taskFactory, AttachmentBuilder::class.java) + for (attachment in attachments) { + builder.apply(arrayOf( + serializer.deserialize(attachment.signerKeys.serialize()), + sandboxBasicInput.apply(attachment.size), + serializer.deserialize(attachment.id.serialize()), + attachment, + sandboxOpenAttachment + )) + } + return builder.apply(null) + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/CommandFactory.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/CommandFactory.kt new file mode 100644 index 0000000000..ccef9690ac --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/CommandFactory.kt @@ -0,0 +1,19 @@ +package net.corda.node.internal.djvm + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.node.djvm.CommandBuilder +import java.util.function.Function + +class CommandFactory( + private val classLoader: SandboxClassLoader, + private val taskFactory: Function> +) { + fun toSandbox(signers: Any?, commands: Any?, partialMerkleLeafIndices: IntArray?): Any? { + val builder = classLoader.createTaskFor(taskFactory, CommandBuilder::class.java) + return builder.apply(arrayOf( + signers, + commands, + partialMerkleLeafIndices + )) + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt new file mode 100644 index 0000000000..9645522baf --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt @@ -0,0 +1,43 @@ +@file:JvmName("ComponentUtils") +package net.corda.node.internal.djvm + +import net.corda.core.contracts.ComponentGroupEnum +import net.corda.core.crypto.componentHash +import net.corda.core.transactions.ComponentGroup +import net.corda.core.transactions.FilteredComponentGroup +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.node.djvm.ComponentBuilder +import java.util.function.Function + +class ComponentFactory( + private val classLoader: SandboxClassLoader, + private val taskFactory: Function>, + private val sandboxBasicInput: Function, + private val serializer: Serializer, + private val componentGroups: List +) { + fun toSandbox( + groupType: ComponentGroupEnum, + clazz: Class<*> + ): Any? { + val components = (componentGroups.firstOrNull(groupType::isSameType) ?: return null).components + val componentBytes = Array(components.size) { idx -> components[idx].bytes } + return classLoader.createTaskFor(taskFactory, ComponentBuilder::class.java).apply(arrayOf( + classLoader.createForImport(serializer.deserializerFor(clazz)), + sandboxBasicInput.apply(groupType), + componentBytes + )) + } + + fun calculateLeafIndicesFor(groupType: ComponentGroupEnum): IntArray? { + val componentGroup = componentGroups.firstOrNull(groupType::isSameType) as? FilteredComponentGroup ?: return null + val componentHashes = componentGroup.components.mapIndexed { index, component -> + componentHash(componentGroup.nonces[index], component) + } + return componentHashes.map { componentGroup.partialMerkleTree.leafIndex(it) }.toIntArray() + } +} + +private fun ComponentGroupEnum.isSameType(group: ComponentGroup): Boolean { + return group.groupIndex == ordinal +} diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt new file mode 100644 index 0000000000..500f656aee --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt @@ -0,0 +1,104 @@ +package net.corda.node.internal.djvm + +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP +import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP +import net.corda.core.contracts.ComponentGroupEnum.SIGNERS_GROUP +import net.corda.core.contracts.TransactionState +import net.corda.core.contracts.TransactionVerificationException +import net.corda.core.crypto.SecureHash +import net.corda.core.internal.ContractVerifier +import net.corda.core.internal.Verifier +import net.corda.core.serialization.serialize +import net.corda.core.transactions.LedgerTransaction +import net.corda.djvm.SandboxConfiguration +import net.corda.djvm.execution.ExecutionSummary +import net.corda.djvm.execution.IsolatedTask +import net.corda.djvm.execution.SandboxException +import net.corda.djvm.messages.Message +import net.corda.djvm.source.ClassSource +import net.corda.node.djvm.LtxFactory + +class DeterministicVerifier( + ltx: LedgerTransaction, + transactionClassLoader: ClassLoader, + private val sandboxConfiguration: SandboxConfiguration +) : Verifier(ltx, transactionClassLoader) { + + override fun verifyContracts() { + val result = IsolatedTask(ltx.id.toString(), sandboxConfiguration).run { + val taskFactory = classLoader.createRawTaskFactory() + val sandboxBasicInput = classLoader.createBasicInput() + + /** + * Deserialise the [LedgerTransaction] again into something + * that we can execute inside the DJVM's sandbox. + */ + val sandboxTx = ltx.transform { componentGroups, serializedInputs, serializedReferences -> + val serializer = Serializer(classLoader) + val componentFactory = ComponentFactory( + classLoader, + taskFactory, + sandboxBasicInput, + serializer, + componentGroups + ) + val attachmentFactory = AttachmentFactory( + classLoader, + taskFactory, + sandboxBasicInput, + serializer + ) + + val idData = ltx.id.serialize() + val notaryData = ltx.notary?.serialize() + val timeWindowData = ltx.timeWindow?.serialize() + val privacySaltData = ltx.privacySalt.serialize() + val networkingParametersData = ltx.networkParameters?.serialize() + + val createSandboxTx = classLoader.createTaskFor(taskFactory, LtxFactory::class.java) + createSandboxTx.apply(arrayOf( + serializer.deserialize(serializedInputs), + componentFactory.toSandbox(OUTPUTS_GROUP, TransactionState::class.java), + CommandFactory(classLoader, taskFactory).toSandbox( + componentFactory.toSandbox(SIGNERS_GROUP, List::class.java), + componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java), + componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP) + ), + attachmentFactory.toSandbox(ltx.attachments), + serializer.deserialize(idData), + serializer.deserialize(notaryData), + serializer.deserialize(timeWindowData), + serializer.deserialize(privacySaltData), + serializer.deserialize(networkingParametersData), + serializer.deserialize(serializedReferences) + )) + } + + val verifier = classLoader.createTaskFor(taskFactory, ContractVerifier::class.java) + + // Now execute the contract verifier task within the sandbox... + verifier.apply(sandboxTx) + } + + with (result.costs) { + logger.info("Verify {} complete: allocations={}, invocations={}, jumps={}, throws={}", + ltx.id, allocations, invocations, jumps, throws) + } + + result.exception?.run { + val sandboxEx = SandboxException( + Message.getMessageFromException(this), + result.identifier, + ClassSource.fromClassName(ContractVerifier::class.java.name), + ExecutionSummary(result.costs), + this + ) + logger.error("Error validating transaction ${ltx.id}.", sandboxEx) + throw DeterministicVerificationException(ltx.id, sandboxEx.message ?: "", sandboxEx) + } + } +} + +class DeterministicVerificationException(txId: SecureHash, message: String, cause: Throwable) + : TransactionVerificationException(txId, message, cause) diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt new file mode 100644 index 0000000000..bfca28b74d --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt @@ -0,0 +1,58 @@ +package net.corda.node.internal.djvm + +import net.corda.core.internal.SerializedStateAndRef +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationFactory +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.serialize +import net.corda.core.utilities.ByteSequence +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.node.djvm.ComponentBuilder +import net.corda.serialization.djvm.createSandboxSerializationEnv +import java.util.function.Function + +class Serializer(private val classLoader: SandboxClassLoader) { + private val factory: SerializationFactory + private val context: SerializationContext + + init { + val env = createSandboxSerializationEnv(classLoader) + factory = env.serializationFactory + context = env.p2pContext + } + + /** + * Convert a list of [SerializedStateAndRef] objects into arrays + * of deserialized sandbox objects. We will pass this array into + * [net.corda.node.djvm.LtxFactory] to be transformed finally to + * a list of [net.corda.core.contracts.StateAndRef] objects, + */ + fun deserialize(stateRefs: List): Array> { + return stateRefs.map { + arrayOf(deserialize(it.serializedState), deserialize(it.ref.serialize())) + }.toTypedArray() + } + + /** + * Generate a [Function] that deserializes a [ByteArray] into an instance + * of the given sandbox class. We import this [Function] into the sandbox + * so that [ComponentBuilder] can deserialize objects lazily. + */ + fun deserializerFor(clazz: Class<*>): Function { + val sandboxClass = classLoader.toSandboxClass(clazz) + return Function { bytes -> + bytes?.run { + factory.deserialize(ByteSequence.of(this), sandboxClass, context) + } + } + } + + fun deserializeTo(clazz: Class<*>, bytes: ByteSequence): Any { + val sandboxClass = classLoader.toSandboxClass(clazz) + return factory.deserialize(bytes, sandboxClass, context) + } + + inline fun deserialize(bytes: SerializedBytes?): Any? { + return deserializeTo(T::class.java, bytes ?: return null) + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 1f914c5c01..ab316aadd7 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -54,8 +54,13 @@ object ConfigHelper { .withFallback(defaultConfig) .resolve() - val entrySet = finalConfig.entrySet().filter { entry -> entry.key.contains("\"") } - for ((key) in entrySet) { + val entrySet = finalConfig.entrySet().filter { entry -> + // System properties can reasonably be expected to contain '.'. However, only + // entries that match this pattern are allowed to contain a '"' character: + // systemProperties."text-without-any-quotes-in" + with(entry.key) { contains("\"") && !matches("^systemProperties\\.\"[^\"]++\"\$".toRegex()) } + } + for (key in entrySet) { log.error("Config files should not contain \" in property names. Please fix: $key") } @@ -63,8 +68,10 @@ object ConfigHelper { } private fun Config.cordaEntriesOnly(): Config { - - return ConfigFactory.parseMap(toProperties().filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) }.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) }) + return ConfigFactory.parseMap(toProperties() + .filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) } + .mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) } + ) } } diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index c6c7ebe54d..f9de2f1391 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -116,15 +116,22 @@ enum class JmxReporterType { JOLOKIA, NEW_RELIC } -data class DevModeOptions(val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker, val allowCompatibilityZone: Boolean = Defaults.disableCheckpointChecker) { - +data class DevModeOptions( + val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker, + val allowCompatibilityZone: Boolean = Defaults.allowCompatibilityZone, + val djvm: DJVMOptions? = null +) { internal object Defaults { - val disableCheckpointChecker = false val allowCompatibilityZone = false } } +data class DJVMOptions( + val bootstrapSource: String?, + val cordaSource: List +) + fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { return this.devMode && this.devModeOptions?.disableCheckpointChecker != true } diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt index 81fc40ff66..62c90766da 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -17,6 +17,7 @@ import net.corda.core.internal.notary.NotaryServiceFlow import net.corda.node.services.config.AuthDataSourceType import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.CertChainPolicyType +import net.corda.node.services.config.DJVMOptions import net.corda.node.services.config.DevModeOptions import net.corda.node.services.config.FlowOverride import net.corda.node.services.config.FlowOverrideConfig @@ -127,9 +128,19 @@ internal object SecurityConfigurationSpec : Configuration.Specification("DevModeOptions") { private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker) private val allowCompatibilityZone by boolean().optional().withDefaultValue(DevModeOptions.Defaults.allowCompatibilityZone) + private val djvm by nested(DJVMOptionsSpec).optional() + + private object DJVMOptionsSpec : Configuration.Specification("DJVMOptions") { + private val bootstrapSource by string().optional() + private val cordaSource by string().list() + + override fun parseValid(configuration: Config): Valid { + return valid(DJVMOptions(configuration[bootstrapSource], configuration[cordaSource])) + } + } override fun parseValid(configuration: Config): Valid { - return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone])) + return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone], configuration[djvm])) } } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt new file mode 100644 index 0000000000..a2a0415eec --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt @@ -0,0 +1,92 @@ +package net.corda.node.services.transactions + +import net.corda.core.internal.BasicVerifier +import net.corda.core.internal.Verifier +import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.transactions.LedgerTransaction +import net.corda.djvm.SandboxConfiguration +import net.corda.djvm.analysis.AnalysisConfiguration +import net.corda.djvm.analysis.Whitelist +import net.corda.djvm.execution.ExecutionProfile +import net.corda.djvm.source.ApiSource +import net.corda.djvm.source.UserPathSource +import net.corda.djvm.source.UserSource +import net.corda.node.internal.djvm.DeterministicVerifier +import java.net.URL +import java.net.URLClassLoader +import java.util.function.UnaryOperator + +interface VerifierFactoryService : UnaryOperator, AutoCloseable + +class DeterministicVerifierFactoryService( + private val bootstrapSource: ApiSource, + private val cordaSource: UserSource +) : SingletonSerializeAsToken(), VerifierFactoryService { + private val baseSandboxConfiguration: SandboxConfiguration + + init { + val baseAnalysisConfiguration = AnalysisConfiguration.createRoot( + userSource = cordaSource, + whitelist = Whitelist.MINIMAL, + visibleAnnotations = setOf( + CordaSerializable::class.java, + ConstructorForDeserialization::class.java, + DeprecatedConstructorForDeserialization::class.java + ), + bootstrapSource = bootstrapSource + ) + + baseSandboxConfiguration = SandboxConfiguration.createFor( + analysisConfiguration = baseAnalysisConfiguration, + profile = NODE_PROFILE, + enableTracing = true + ) + } + + /** + * Generate sandbox classes for every Corda jar with META-INF/DJVM-preload. + */ + fun generateSandbox(): DeterministicVerifierFactoryService { + baseSandboxConfiguration.preload() + return this + } + + override fun apply(ledgerTransaction: LedgerTransaction): LedgerTransaction { + // Specialise the LedgerTransaction here so that + // contracts are verified inside the DJVM! + return ledgerTransaction.specialise(::specialise) + } + + private fun specialise(ltx: LedgerTransaction, classLoader: ClassLoader): Verifier { + return (classLoader as? URLClassLoader)?.run { + DeterministicVerifier(ltx, classLoader, createSandbox(classLoader.urLs)) + } ?: BasicVerifier(ltx, classLoader) + } + + private fun createSandbox(userSource: Array): SandboxConfiguration { + return baseSandboxConfiguration.createChild(UserPathSource(userSource)) + } + + override fun close() { + bootstrapSource.use { + cordaSource.close() + } + } + + private companion object { + private val NODE_PROFILE = ExecutionProfile( + allocationCostThreshold = 1024 * 1024 * 1024, + invocationCostThreshold = 100_000_000, + jumpCostThreshold = 500_000_000, + throwCostThreshold = 1_000_000 + ) + } +} + +class BasicVerifierFactoryService : VerifierFactoryService { + override fun apply(ledgerTransaction: LedgerTransaction)= ledgerTransaction + override fun close() {} +} diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index c63daa7726..7a17efd6a8 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -54,6 +54,7 @@ task patchSerialization(type: Zip, dependsOn: serializationJarTask) { archiveExtension = 'jar' from(compileKotlin) + from(processResources) from(zipTree(originalJar)) { exclude 'net/corda/serialization/internal/AttachmentsClassLoaderBuilder*' exclude 'net/corda/serialization/internal/ByteBufferStreams*' diff --git a/serialization-deterministic/src/main/resources/META-INF/DJVM-preload b/serialization-deterministic/src/main/resources/META-INF/DJVM-preload new file mode 100644 index 0000000000..e69de29bb2 diff --git a/serialization-djvm/build.gradle b/serialization-djvm/build.gradle new file mode 100644 index 0000000000..b85e003547 --- /dev/null +++ b/serialization-djvm/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'net.corda.plugins.publish-utils' + id 'com.jfrog.artifactory' + id 'java-library' + id 'idea' +} + +description 'Serialization support for the DJVM' + +configurations { + sandboxTesting + jdkRt.resolutionStrategy { + // Always check the repository for a newer SNAPSHOT. + cacheChangingModulesFor 0, 'seconds' + } +} + +dependencies { + api project(':core') + api project(':serialization') + api "net.corda.djvm:corda-djvm:$djvm_version" + implementation(project(':serialization-djvm:deserializers')) { + transitive = false + } + + testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version" + testImplementation "org.junit.jupiter:junit-jupiter-params:$junit_jupiter_version" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version" + + // Test utilities + testImplementation "org.assertj:assertj-core:$assertj_version" + testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + jdkRt "net.corda:deterministic-rt:latest.integration" + + // The DJVM will need this classpath to run the unit tests. + sandboxTesting files(sourceSets.getByName("test").output) + sandboxTesting project(':serialization-djvm:deserializers') + sandboxTesting project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') + sandboxTesting "org.slf4j:slf4j-nop:$slf4j_version" +} + +jar { + archiveBaseName = 'corda-serialization-djvm' + manifest { + attributes('Automatic-Module-Name': 'net.corda.serialization.djvm') + attributes('Sealed': true) + } +} + +tasks.withType(Test) { + useJUnitPlatform() + systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath + systemProperty 'sandbox-libraries.path', configurations.sandboxTesting.asPath + + // Configure the host timezone to match the DJVM's. + systemProperty 'user.timezone', 'UTC' +} + +publish { + name jar.archiveBaseName.get() +} + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} diff --git a/serialization-djvm/deserializers/build.gradle b/serialization-djvm/deserializers/build.gradle new file mode 100644 index 0000000000..5676207fd1 --- /dev/null +++ b/serialization-djvm/deserializers/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'net.corda.plugins.publish-utils' + id 'com.jfrog.artifactory' + id 'java-library' + id 'idea' +} +apply from: '../../deterministic.gradle' + +description 'Deserializers for the DJVM' + +dependencies { + api project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + api project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') +} + +jar { + archiveBaseName = 'corda-deserializers-djvm' + manifest { + attributes('Automatic-Module-Name': 'net.corda.serialization.djvm.deserializers') + attributes('Sealed': true) + } +} + +publish { + name jar.archiveBaseName.get() +} + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/BitSetDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/BitSetDeserializer.kt new file mode 100644 index 0000000000..718018e8b5 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/BitSetDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.BitSetSerializer.BitSetProxy +import java.util.BitSet +import java.util.function.Function + +class BitSetDeserializer : Function { + override fun apply(proxy: BitSetProxy): BitSet { + return BitSet.valueOf(proxy.bytes) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CertPathDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CertPathDeserializer.kt new file mode 100644 index 0000000000..04d03a2109 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CertPathDeserializer.kt @@ -0,0 +1,13 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.CertPathSerializer.CertPathProxy +import java.security.cert.CertPath +import java.security.cert.CertificateFactory +import java.util.function.Function + +class CertPathDeserializer : Function { + override fun apply(proxy: CertPathProxy): CertPath { + val factory = CertificateFactory.getInstance(proxy.type) + return factory.generateCertPath(proxy.encoded.inputStream()) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ClassDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ClassDeserializer.kt new file mode 100644 index 0000000000..6f8c03a33c --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ClassDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy + +import java.util.function.Function + +class ClassDeserializer : Function> { + override fun apply(proxy: ClassProxy): Class<*> { + return Class.forName(proxy.className) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateCollection.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateCollection.kt new file mode 100644 index 0000000000..18a2933ec7 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateCollection.kt @@ -0,0 +1,50 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.core.utilities.NonEmptySet +import java.util.Collections +import java.util.NavigableSet +import java.util.SortedSet +import java.util.TreeSet +import java.util.function.Function + +class CreateCollection : Function, Collection> { + private val concreteConstructors: Map>, (Array) -> Collection> = mapOf( + List::class.java to ::createList, + Set::class.java to ::createSet, + SortedSet::class.java to ::createSortedSet, + NavigableSet::class.java to ::createNavigableSet, + Collection::class.java to ::createCollection, + NonEmptySet::class.java to ::createNonEmptySet + ) + + private fun createList(values: Array): List { + return Collections.unmodifiableList(values.toCollection(ArrayList())) + } + + private fun createSet(values: Array): Set { + return Collections.unmodifiableSet(values.toCollection(LinkedHashSet())) + } + + private fun createSortedSet(values: Array): SortedSet { + return Collections.unmodifiableSortedSet(values.toCollection(TreeSet())) + } + + private fun createNavigableSet(values: Array): NavigableSet { + return Collections.unmodifiableNavigableSet(values.toCollection(TreeSet())) + } + + private fun createCollection(values: Array): Collection { + return Collections.unmodifiableCollection(values.toCollection(ArrayList())) + } + + private fun createNonEmptySet(values: Array): NonEmptySet { + return NonEmptySet.copyOf(values.toCollection(ArrayList())) + } + + @Suppress("unchecked_cast") + override fun apply(inputs: Array): Collection { + val collectionClass = inputs[0] as Class> + val args = inputs[1] as Array + return concreteConstructors[collectionClass]?.invoke(args)!! + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateCurrency.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateCurrency.kt new file mode 100644 index 0000000000..0fc21d8750 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateCurrency.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import java.util.Currency +import java.util.function.Function + +class CreateCurrency : Function { + override fun apply(currencyCode: String): Currency { + return Currency.getInstance(currencyCode) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateFromString.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateFromString.kt new file mode 100644 index 0000000000..4981eeccb8 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateFromString.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import java.lang.reflect.Constructor +import java.util.function.Function + +class CreateFromString(private val factory: Constructor) : Function { + override fun apply(text: String): Any { + return factory.newInstance(text) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateMap.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateMap.kt new file mode 100644 index 0000000000..6abc37901e --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/CreateMap.kt @@ -0,0 +1,52 @@ +package net.corda.serialization.djvm.deserializers + +import java.util.Collections +import java.util.EnumMap +import java.util.NavigableMap +import java.util.SortedMap +import java.util.TreeMap +import java.util.function.Function + +class CreateMap : Function, Map> { + private val concreteConstructors: Map>, (Array>) -> Map> = mapOf( + Map::class.java to ::createMap, + SortedMap::class.java to ::createSortedMap, + LinkedHashMap::class.java to ::createLinkedHashMap, + NavigableMap::class.java to ::createNavigableMap, + TreeMap::class.java to ::createTreeMap, + EnumMap::class.java to ::createEnumMap + ) + + private fun createMap(values: Array>): Map { + return Collections.unmodifiableMap(values.map { it[0] to it[1] }.toMap()) + } + + private fun createSortedMap(values: Array>): SortedMap { + return Collections.unmodifiableSortedMap(createTreeMap(values)) + } + + private fun createNavigableMap(values: Array>): NavigableMap { + return Collections.unmodifiableNavigableMap(createTreeMap(values)) + } + + private fun createLinkedHashMap(values: Array>): LinkedHashMap { + return values.map { it[0] to it[1] }.toMap(LinkedHashMap()) + } + + private fun createTreeMap(values: Array>): TreeMap { + return values.map { it[0] to it[1] }.toMap(TreeMap()) + } + + private fun createEnumMap(values: Array>): Map { + val map = values.map { it[0] to it[1] }.toMap() + @Suppress("unchecked_cast") + return EnumMap(map as Map) as Map + } + + @Suppress("unchecked_cast") + override fun apply(inputs: Array): Map { + val mapClass = inputs[0] as Class> + val args = inputs[1] as Array> + return concreteConstructors[mapClass]?.invoke(args)!! + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal128Deserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal128Deserializer.kt new file mode 100644 index 0000000000..6fec4a512f --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal128Deserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.Decimal128 +import java.util.function.Function + +class Decimal128Deserializer : Function { + override fun apply(underlying: LongArray): Decimal128 { + return Decimal128(underlying[0], underlying[1]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal32Deserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal32Deserializer.kt new file mode 100644 index 0000000000..cdf0ac17d9 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal32Deserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.Decimal32 +import java.util.function.Function + +class Decimal32Deserializer : Function { + override fun apply(underlying: IntArray): Decimal32 { + return Decimal32(underlying[0]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal64Deserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal64Deserializer.kt new file mode 100644 index 0000000000..b481278218 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/Decimal64Deserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.Decimal64 +import java.util.function.Function + +class Decimal64Deserializer : Function { + override fun apply(underlying: LongArray): Decimal64 { + return Decimal64(underlying[0]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/DescribeEnum.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/DescribeEnum.kt new file mode 100644 index 0000000000..4e0303f897 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/DescribeEnum.kt @@ -0,0 +1,9 @@ +package net.corda.serialization.djvm.deserializers + +import java.util.function.Function + +class DescribeEnum : Function, Array> { + override fun apply(enumClass: Class<*>): Array { + return enumClass.enumConstants + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/DurationDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/DurationDeserializer.kt new file mode 100644 index 0000000000..e75d197a76 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/DurationDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.DurationSerializer.DurationProxy +import java.time.Duration +import java.util.function.Function + +class DurationDeserializer : Function { + override fun apply(proxy: DurationProxy): Duration { + return Duration.ofSeconds(proxy.seconds, proxy.nanos.toLong()) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/EnumSetDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/EnumSetDeserializer.kt new file mode 100644 index 0000000000..4020c3cd3b --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/EnumSetDeserializer.kt @@ -0,0 +1,16 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.core.internal.uncheckedCast +import net.corda.serialization.internal.amqp.custom.EnumSetSerializer.EnumSetProxy +import java.util.EnumSet +import java.util.function.Function + +class EnumSetDeserializer : Function> { + override fun apply(proxy: EnumSetProxy): EnumSet<*> { + return if (proxy.elements.isEmpty()) { + EnumSet.noneOf(uncheckedCast, Class>(proxy.clazz)) + } else { + EnumSet.copyOf(uncheckedCast, List>(proxy.elements)) + } + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/InputStreamDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/InputStreamDeserializer.kt new file mode 100644 index 0000000000..1aabb9fbe4 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/InputStreamDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.util.function.Function + +class InputStreamDeserializer : Function { + override fun apply(bytes: ByteArray): InputStream? { + return ByteArrayInputStream(bytes) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/InstantDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/InstantDeserializer.kt new file mode 100644 index 0000000000..bd5e61810b --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/InstantDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.InstantSerializer.InstantProxy +import java.time.Instant +import java.util.function.Function + +class InstantDeserializer : Function { + override fun apply(proxy: InstantProxy): Instant { + return Instant.ofEpochSecond(proxy.epochSeconds, proxy.nanos.toLong()) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/JustForCasting.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/JustForCasting.kt new file mode 100644 index 0000000000..2eb1b07acf --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/JustForCasting.kt @@ -0,0 +1,6 @@ +package net.corda.serialization.djvm.deserializers + +@Suppress("unused") +enum class JustForCasting { + UNUSED +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalDateDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalDateDeserializer.kt new file mode 100644 index 0000000000..5da4fb3b83 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalDateDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.LocalDateSerializer.LocalDateProxy +import java.time.LocalDate +import java.util.function.Function + +class LocalDateDeserializer : Function { + override fun apply(proxy: LocalDateProxy): LocalDate { + return LocalDate.of(proxy.year, proxy.month.toInt(), proxy.day.toInt()) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalDateTimeDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalDateTimeDeserializer.kt new file mode 100644 index 0000000000..2123dc81e4 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalDateTimeDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.LocalDateTimeSerializer.LocalDateTimeProxy +import java.time.LocalDateTime +import java.util.function.Function + +class LocalDateTimeDeserializer : Function { + override fun apply(proxy: LocalDateTimeProxy): LocalDateTime { + return LocalDateTime.of(proxy.date, proxy.time) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalTimeDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalTimeDeserializer.kt new file mode 100644 index 0000000000..e0d7c76d2a --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/LocalTimeDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.LocalTimeSerializer.LocalTimeProxy +import java.time.LocalTime +import java.util.function.Function + +class LocalTimeDeserializer : Function { + override fun apply(proxy: LocalTimeProxy): LocalTime { + return LocalTime.of(proxy.hour.toInt(), proxy.minute.toInt(), proxy.second.toInt(), proxy.nano) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/MonthDayDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/MonthDayDeserializer.kt new file mode 100644 index 0000000000..59195d0729 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/MonthDayDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.MonthDaySerializer.MonthDayProxy +import java.time.MonthDay +import java.util.function.Function + +class MonthDayDeserializer : Function { + override fun apply(proxy: MonthDayProxy): MonthDay { + return MonthDay.of(proxy.month.toInt(), proxy.day.toInt()) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OffsetDateTimeDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OffsetDateTimeDeserializer.kt new file mode 100644 index 0000000000..275ddd8fa9 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OffsetDateTimeDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer.OffsetDateTimeProxy +import java.time.OffsetDateTime +import java.util.function.Function + +class OffsetDateTimeDeserializer : Function { + override fun apply(proxy: OffsetDateTimeProxy): OffsetDateTime { + return OffsetDateTime.of(proxy.dateTime, proxy.offset) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OffsetTimeDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OffsetTimeDeserializer.kt new file mode 100644 index 0000000000..550c565355 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OffsetTimeDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer.OffsetTimeProxy +import java.time.OffsetTime +import java.util.function.Function + +class OffsetTimeDeserializer : Function { + override fun apply(proxy: OffsetTimeProxy): OffsetTime { + return OffsetTime.of(proxy.time, proxy.offset) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OpaqueBytesSubSequenceDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OpaqueBytesSubSequenceDeserializer.kt new file mode 100644 index 0000000000..9755d1dae0 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OpaqueBytesSubSequenceDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.OpaqueBytesSubSequence +import java.util.function.Function + +class OpaqueBytesSubSequenceDeserializer : Function { + override fun apply(proxy: OpaqueBytes): OpaqueBytesSubSequence { + return OpaqueBytesSubSequence(proxy.bytes, proxy.offset, proxy.size) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OptionalDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OptionalDeserializer.kt new file mode 100644 index 0000000000..c9f93b43a1 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/OptionalDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.OptionalSerializer.OptionalProxy +import java.util.Optional +import java.util.function.Function + +class OptionalDeserializer : Function> { + override fun apply(proxy: OptionalProxy): Optional { + return Optional.ofNullable(proxy.item) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/PeriodDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/PeriodDeserializer.kt new file mode 100644 index 0000000000..25e6381455 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/PeriodDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.PeriodSerializer.PeriodProxy +import java.time.Period +import java.util.function.Function + +class PeriodDeserializer : Function { + override fun apply(proxy: PeriodProxy): Period { + return Period.of(proxy.years, proxy.months, proxy.days) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/PublicKeyDecoder.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/PublicKeyDecoder.kt new file mode 100644 index 0000000000..1be04bd88a --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/PublicKeyDecoder.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.core.crypto.Crypto +import java.security.PublicKey +import java.util.function.Function + +class PublicKeyDecoder : Function { + override fun apply(encoded: ByteArray): PublicKey { + return Crypto.decodePublicKey(encoded) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/SymbolDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/SymbolDeserializer.kt new file mode 100644 index 0000000000..83f058b231 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/SymbolDeserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.Symbol +import java.util.function.Function + +class SymbolDeserializer : Function { + override fun apply(value: String): Symbol { + return Symbol.valueOf(value) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedByteDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedByteDeserializer.kt new file mode 100644 index 0000000000..facf725c4a --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedByteDeserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.UnsignedByte +import java.util.function.Function + +class UnsignedByteDeserializer : Function { + override fun apply(underlying: ByteArray): UnsignedByte { + return UnsignedByte(underlying[0]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedIntegerDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedIntegerDeserializer.kt new file mode 100644 index 0000000000..69732c58ff --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedIntegerDeserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.UnsignedInteger +import java.util.function.Function + +class UnsignedIntegerDeserializer : Function { + override fun apply(underlying: IntArray): UnsignedInteger { + return UnsignedInteger(underlying[0]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedLongDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedLongDeserializer.kt new file mode 100644 index 0000000000..b628b033e8 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedLongDeserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.UnsignedLong +import java.util.function.Function + +class UnsignedLongDeserializer : Function { + override fun apply(underlying: LongArray): UnsignedLong { + return UnsignedLong(underlying[0]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedShortDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedShortDeserializer.kt new file mode 100644 index 0000000000..5251225f21 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/UnsignedShortDeserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import org.apache.qpid.proton.amqp.UnsignedShort +import java.util.function.Function + +class UnsignedShortDeserializer : Function { + override fun apply(underlying: ShortArray): UnsignedShort { + return UnsignedShort(underlying[0]) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/X509CRLDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/X509CRLDeserializer.kt new file mode 100644 index 0000000000..81309fcfbf --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/X509CRLDeserializer.kt @@ -0,0 +1,12 @@ +package net.corda.serialization.djvm.deserializers + +import java.security.cert.CertificateFactory +import java.security.cert.X509CRL +import java.util.function.Function + +class X509CRLDeserializer : Function { + override fun apply(bytes: ByteArray): X509CRL { + val factory = CertificateFactory.getInstance("X.509") + return factory.generateCRL(bytes.inputStream()) as X509CRL + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/X509CertificateDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/X509CertificateDeserializer.kt new file mode 100644 index 0000000000..9f3423eaed --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/X509CertificateDeserializer.kt @@ -0,0 +1,12 @@ +package net.corda.serialization.djvm.deserializers + +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.util.function.Function + +class X509CertificateDeserializer : Function { + override fun apply(bits: ByteArray): X509Certificate { + val factory = CertificateFactory.getInstance("X.509") + return factory.generateCertificate(bits.inputStream()) as X509Certificate + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/YearDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/YearDeserializer.kt new file mode 100644 index 0000000000..2787e587a2 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/YearDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.YearSerializer.YearProxy +import java.time.Year +import java.util.function.Function + +class YearDeserializer : Function { + override fun apply(proxy: YearProxy): Year { + return Year.of(proxy.year) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/YearMonthDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/YearMonthDeserializer.kt new file mode 100644 index 0000000000..6876aa4082 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/YearMonthDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.YearMonthSerializer.YearMonthProxy +import java.time.YearMonth +import java.util.function.Function + +class YearMonthDeserializer : Function { + override fun apply(proxy: YearMonthProxy): YearMonth { + return YearMonth.of(proxy.year, proxy.month.toInt()) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ZoneIdDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ZoneIdDeserializer.kt new file mode 100644 index 0000000000..c28dbcab24 --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ZoneIdDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.ZoneIdSerializer.ZoneIdProxy +import java.time.ZoneId +import java.util.function.Function + +class ZoneIdDeserializer : Function { + override fun apply(proxy: ZoneIdProxy): ZoneId { + return ZoneId.of(proxy.id) + } +} diff --git a/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ZonedDateTimeDeserializer.kt b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ZonedDateTimeDeserializer.kt new file mode 100644 index 0000000000..e1cf9485ee --- /dev/null +++ b/serialization-djvm/deserializers/src/main/kotlin/net/corda/serialization/djvm/deserializers/ZonedDateTimeDeserializer.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm.deserializers + +import net.corda.serialization.internal.amqp.custom.ZonedDateTimeSerializer.ZonedDateTimeProxy +import java.util.function.Function + +class ZonedDateTimeDeserializer : Function?> { + override fun apply(proxy: ZonedDateTimeProxy): Array? { + return arrayOf(proxy.dateTime, proxy.offset, proxy.zone) + } +} diff --git a/serialization-djvm/deserializers/src/main/resources/META-INF/DJVM-preload b/serialization-djvm/deserializers/src/main/resources/META-INF/DJVM-preload new file mode 100644 index 0000000000..e69de29bb2 diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/AMQPSerializationScheme.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/AMQPSerializationScheme.kt new file mode 100644 index 0000000000..7b2404b6eb --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/AMQPSerializationScheme.kt @@ -0,0 +1,131 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationContext.UseCase +import net.corda.core.serialization.SerializedBytes +import net.corda.core.utilities.ByteSequence +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.serializers.SandboxBitSetSerializer +import net.corda.serialization.djvm.serializers.SandboxCertPathSerializer +import net.corda.serialization.djvm.serializers.SandboxCharacterSerializer +import net.corda.serialization.djvm.serializers.SandboxCollectionSerializer +import net.corda.serialization.djvm.serializers.SandboxCurrencySerializer +import net.corda.serialization.djvm.serializers.SandboxDecimal128Serializer +import net.corda.serialization.djvm.serializers.SandboxDecimal32Serializer +import net.corda.serialization.djvm.serializers.SandboxDecimal64Serializer +import net.corda.serialization.djvm.serializers.SandboxDurationSerializer +import net.corda.serialization.djvm.serializers.SandboxEnumSerializer +import net.corda.serialization.djvm.serializers.SandboxEnumSetSerializer +import net.corda.serialization.djvm.serializers.SandboxInputStreamSerializer +import net.corda.serialization.djvm.serializers.SandboxInstantSerializer +import net.corda.serialization.djvm.serializers.SandboxLocalDateSerializer +import net.corda.serialization.djvm.serializers.SandboxLocalDateTimeSerializer +import net.corda.serialization.djvm.serializers.SandboxLocalTimeSerializer +import net.corda.serialization.djvm.serializers.SandboxMapSerializer +import net.corda.serialization.djvm.serializers.SandboxMonthDaySerializer +import net.corda.serialization.djvm.serializers.SandboxOffsetDateTimeSerializer +import net.corda.serialization.djvm.serializers.SandboxOffsetTimeSerializer +import net.corda.serialization.djvm.serializers.SandboxOpaqueBytesSubSequenceSerializer +import net.corda.serialization.djvm.serializers.SandboxOptionalSerializer +import net.corda.serialization.djvm.serializers.SandboxPeriodSerializer +import net.corda.serialization.djvm.serializers.SandboxPrimitiveSerializer +import net.corda.serialization.djvm.serializers.SandboxPublicKeySerializer +import net.corda.serialization.djvm.serializers.SandboxSymbolSerializer +import net.corda.serialization.djvm.serializers.SandboxToStringSerializer +import net.corda.serialization.djvm.serializers.SandboxUnsignedByteSerializer +import net.corda.serialization.djvm.serializers.SandboxUnsignedIntegerSerializer +import net.corda.serialization.djvm.serializers.SandboxUnsignedLongSerializer +import net.corda.serialization.djvm.serializers.SandboxUnsignedShortSerializer +import net.corda.serialization.djvm.serializers.SandboxX509CRLSerializer +import net.corda.serialization.djvm.serializers.SandboxX509CertificateSerializer +import net.corda.serialization.djvm.serializers.SandboxYearMonthSerializer +import net.corda.serialization.djvm.serializers.SandboxYearSerializer +import net.corda.serialization.djvm.serializers.SandboxZoneIdSerializer +import net.corda.serialization.djvm.serializers.SandboxZonedDateTimeSerializer +import net.corda.serialization.internal.CordaSerializationMagic +import net.corda.serialization.internal.SerializationScheme +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.SerializerFactoryFactory +import net.corda.serialization.internal.amqp.amqpMagic +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Date +import java.util.UUID +import java.util.function.Function + +class AMQPSerializationScheme( + private val classLoader: SandboxClassLoader, + private val sandboxBasicInput: Function, + private val taskFactory: Function>, + private val serializerFactoryFactory: SerializerFactoryFactory +) : SerializationScheme { + + private fun getSerializerFactory(context: SerializationContext): SerializerFactory { + return serializerFactoryFactory.make(context).apply { + register(SandboxBitSetSerializer(classLoader, taskFactory, this)) + register(SandboxCertPathSerializer(classLoader, taskFactory, this)) + register(SandboxDurationSerializer(classLoader, taskFactory, this)) + register(SandboxEnumSetSerializer(classLoader, taskFactory, this)) + register(SandboxInputStreamSerializer(classLoader, taskFactory)) + register(SandboxInstantSerializer(classLoader, taskFactory, this)) + register(SandboxLocalDateSerializer(classLoader, taskFactory, this)) + register(SandboxLocalDateTimeSerializer(classLoader, taskFactory, this)) + register(SandboxLocalTimeSerializer(classLoader, taskFactory, this)) + register(SandboxMonthDaySerializer(classLoader, taskFactory, this)) + register(SandboxOffsetDateTimeSerializer(classLoader, taskFactory, this)) + register(SandboxOffsetTimeSerializer(classLoader, taskFactory, this)) + register(SandboxPeriodSerializer(classLoader, taskFactory, this)) + register(SandboxYearMonthSerializer(classLoader, taskFactory, this)) + register(SandboxYearSerializer(classLoader, taskFactory, this)) + register(SandboxZonedDateTimeSerializer(classLoader, taskFactory, this)) + register(SandboxZoneIdSerializer(classLoader, taskFactory, this)) + register(SandboxOpaqueBytesSubSequenceSerializer(classLoader, taskFactory, this)) + register(SandboxOptionalSerializer(classLoader, taskFactory, this)) + register(SandboxPrimitiveSerializer(UUID::class.java, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(String::class.java, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Byte::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Short::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Int::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Long::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Float::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Double::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Boolean::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxPrimitiveSerializer(Date::class.javaObjectType, classLoader, sandboxBasicInput)) + register(SandboxCharacterSerializer(classLoader, sandboxBasicInput)) + register(SandboxCollectionSerializer(classLoader, taskFactory, this)) + register(SandboxMapSerializer(classLoader, taskFactory, this)) + register(SandboxEnumSerializer(classLoader, taskFactory, this)) + register(SandboxPublicKeySerializer(classLoader, taskFactory)) + register(SandboxToStringSerializer(BigDecimal::class.java, classLoader, taskFactory, sandboxBasicInput)) + register(SandboxToStringSerializer(BigInteger::class.java, classLoader, taskFactory, sandboxBasicInput)) + register(SandboxToStringSerializer(StringBuffer::class.java, classLoader, taskFactory, sandboxBasicInput)) + register(SandboxCurrencySerializer(classLoader, taskFactory, sandboxBasicInput)) + register(SandboxX509CertificateSerializer(classLoader, taskFactory)) + register(SandboxX509CRLSerializer(classLoader, taskFactory)) + register(SandboxUnsignedLongSerializer(classLoader, taskFactory)) + register(SandboxUnsignedIntegerSerializer(classLoader, taskFactory)) + register(SandboxUnsignedShortSerializer(classLoader, taskFactory)) + register(SandboxUnsignedByteSerializer(classLoader, taskFactory)) + register(SandboxDecimal128Serializer(classLoader, taskFactory)) + register(SandboxDecimal64Serializer(classLoader, taskFactory)) + register(SandboxDecimal32Serializer(classLoader, taskFactory)) + register(SandboxSymbolSerializer(classLoader, taskFactory, sandboxBasicInput)) + } + } + + override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { + val serializerFactory = getSerializerFactory(context) + return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context) + } + + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { + val serializerFactory = getSerializerFactory(context) + return SerializationOutput(serializerFactory).serialize(obj, context) + } + + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: UseCase): Boolean { + return magic == amqpMagic && target == UseCase.P2P + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/DelegatingClassLoader.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/DelegatingClassLoader.kt new file mode 100644 index 0000000000..9be12a86b2 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/DelegatingClassLoader.kt @@ -0,0 +1,10 @@ +package net.corda.serialization.djvm + +import net.corda.djvm.rewiring.SandboxClassLoader + +class DelegatingClassLoader(private val delegate: SandboxClassLoader) : ClassLoader(null) { + @Throws(ClassNotFoundException::class) + override fun loadClass(name: String, resolve: Boolean): Class<*> { + return delegate.loadForSandbox(name).type + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/SandboxSerializerFactoryFactory.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/SandboxSerializerFactoryFactory.kt new file mode 100644 index 0000000000..57028d0deb --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/SandboxSerializerFactoryFactory.kt @@ -0,0 +1,108 @@ +@file:Suppress("platform_class_mapped_to_kotlin") +package net.corda.serialization.djvm + +import net.corda.core.serialization.SerializationContext +import net.corda.serialization.internal.amqp.AMQPRemoteTypeModel +import net.corda.serialization.internal.amqp.AMQPSerializer +import net.corda.serialization.internal.amqp.CachingCustomSerializerRegistry +import net.corda.serialization.internal.amqp.ComposedSerializerFactory +import net.corda.serialization.internal.amqp.DefaultDescriptorBasedSerializerRegistry +import net.corda.serialization.internal.amqp.DefaultEvolutionSerializerFactory +import net.corda.serialization.internal.amqp.DefaultLocalSerializerFactory +import net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.SerializerFactoryFactory +import net.corda.serialization.internal.amqp.WhitelistBasedTypeModelConfiguration +import net.corda.serialization.internal.amqp.createClassCarpenter +import net.corda.serialization.internal.model.ClassCarpentingTypeLoader +import net.corda.serialization.internal.model.ConfigurableLocalTypeModel +import net.corda.serialization.internal.model.SchemaBuildingRemoteTypeCarpenter +import net.corda.serialization.internal.model.TypeLoader +import net.corda.serialization.internal.model.TypeModellingFingerPrinter +import java.lang.Boolean +import java.lang.Byte +import java.lang.Double +import java.lang.Float +import java.lang.Long +import java.lang.Short +import java.util.Collections.singleton +import java.util.Collections.unmodifiableMap +import java.util.Date +import java.util.UUID +import java.util.function.Function +import java.util.function.Predicate + +/** + * This has all been lovingly copied from [SerializerFactoryBuilder]. + */ +class SandboxSerializerFactoryFactory( + private val primitiveSerializerFactory: Function, AMQPSerializer> +) : SerializerFactoryFactory { + + override fun make(context: SerializationContext): SerializerFactory { + val classLoader = context.deserializationClassLoader + + val primitiveTypes = unmodifiableMap(mapOf, Class<*>>( + classLoader.loadClass("sandbox.java.lang.Boolean") to Boolean.TYPE, + classLoader.loadClass("sandbox.java.lang.Byte") to Byte.TYPE, + classLoader.loadClass("sandbox.java.lang.Character") to Character.TYPE, + classLoader.loadClass("sandbox.java.lang.Double") to Double.TYPE, + classLoader.loadClass("sandbox.java.lang.Float") to Float.TYPE, + classLoader.loadClass("sandbox.java.lang.Integer") to Integer.TYPE, + classLoader.loadClass("sandbox.java.lang.Long") to Long.TYPE, + classLoader.loadClass("sandbox.java.lang.Short") to Short.TYPE, + classLoader.loadClass("sandbox.java.lang.String") to String::class.java, + classLoader.loadClass("sandbox.java.util.Date") to Date::class.java, + classLoader.loadClass("sandbox.java.util.UUID") to UUID::class.java, + Void::class.java to Void.TYPE + )) + + val classCarpenter = createClassCarpenter(context) + val descriptorBasedSerializerRegistry = DefaultDescriptorBasedSerializerRegistry() + val customSerializerRegistry = CachingCustomSerializerRegistry( + descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry, + allowedFor = singleton(classLoader.loadClass("sandbox.java.lang.Object")) + ) + + val localTypeModel = ConfigurableLocalTypeModel( + WhitelistBasedTypeModelConfiguration(context.whitelist, customSerializerRegistry) + ) + + val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry) + + val localSerializerFactory = DefaultLocalSerializerFactory( + whitelist = context.whitelist, + typeModel = localTypeModel, + fingerPrinter = fingerPrinter, + classloader = classLoader, + descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry, + primitiveSerializerFactory = primitiveSerializerFactory, + isPrimitiveType = Predicate { clazz -> clazz.isPrimitive || clazz in primitiveTypes.keys }, + customSerializerRegistry = customSerializerRegistry, + onlyCustomSerializers = false + ) + + val typeLoader: TypeLoader = ClassCarpentingTypeLoader( + carpenter = SchemaBuildingRemoteTypeCarpenter(classCarpenter), + classLoader = classLoader + ) + + val evolutionSerializerFactory = DefaultEvolutionSerializerFactory( + localSerializerFactory = localSerializerFactory, + classLoader = classLoader, + mustPreserveDataWhenEvolving = context.preventDataLoss, + primitiveTypes = primitiveTypes + ) + + val remoteSerializerFactory = DefaultRemoteSerializerFactory( + evolutionSerializerFactory = evolutionSerializerFactory, + descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry, + remoteTypeModel = AMQPRemoteTypeModel(), + localTypeModel = localTypeModel, + typeLoader = typeLoader, + localSerializerFactory = localSerializerFactory + ) + + return ComposedSerializerFactory(localSerializerFactory, remoteSerializerFactory, customSerializerRegistry) + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/SandboxWhitelist.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/SandboxWhitelist.kt new file mode 100644 index 0000000000..9df1b34cb0 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/SandboxWhitelist.kt @@ -0,0 +1,13 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.ClassWhitelist + +class SandboxWhitelist : ClassWhitelist { + companion object { + private val packageName = "^sandbox\\.(?:java|kotlin)(?:[.]|$)".toRegex() + } + + override fun hasListed(type: Class<*>): Boolean { + return packageName.containsMatchIn(type.`package`.name) + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt new file mode 100644 index 0000000000..77c72f2793 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/Serialization.kt @@ -0,0 +1,74 @@ +@file:JvmName("Serialization") +package net.corda.serialization.djvm + +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationContext.UseCase +import net.corda.core.serialization.SerializationFactory +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.internal.SerializationEnvironment +import net.corda.core.utilities.ByteSequence +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.serializers.PrimitiveSerializer +import net.corda.serialization.internal.GlobalTransientClassWhiteList +import net.corda.serialization.internal.SerializationContextImpl +import net.corda.serialization.internal.SerializationFactoryImpl +import net.corda.serialization.internal.amqp.AMQPSerializer +import net.corda.serialization.internal.amqp.amqpMagic +import java.util.function.Function + +@Suppress("NOTHING_TO_INLINE") +inline fun SandboxClassLoader.toSandboxAnyClass(clazz: Class<*>): Class { + @Suppress("unchecked_cast") + return toSandboxClass(clazz) as Class +} + +fun createSandboxSerializationEnv(classLoader: SandboxClassLoader): SerializationEnvironment { + val p2pContext: SerializationContext = SerializationContextImpl( + preferredSerializationVersion = amqpMagic, + deserializationClassLoader = DelegatingClassLoader(classLoader), + whitelist = GlobalTransientClassWhiteList(SandboxWhitelist()), + properties = emptyMap(), + objectReferencesEnabled = true, + carpenterDisabled = true, + useCase = UseCase.P2P, + encoding = null + ) + + val taskFactory = classLoader.createRawTaskFactory() + val sandboxBasicInput = classLoader.createBasicInput() + + val primitiveSerializerFactory: Function, AMQPSerializer> = Function { clazz -> + PrimitiveSerializer(clazz, sandboxBasicInput) + } + + val factory = SerializationFactoryImpl(mutableMapOf()).apply { + registerScheme(AMQPSerializationScheme( + classLoader = classLoader, + sandboxBasicInput = sandboxBasicInput, + taskFactory = taskFactory, + serializerFactoryFactory = SandboxSerializerFactoryFactory(primitiveSerializerFactory) + )) + } + return SerializationEnvironment.with(factory, p2pContext = p2pContext) +} + +inline fun SerializedBytes.deserializeFor(classLoader: SandboxClassLoader): Any { + return deserializeTo(T::class.java, classLoader) +} + +inline fun ByteSequence.deserializeTypeFor(classLoader: SandboxClassLoader): Any { + return deserializeTo(T::class.java, classLoader) +} + +fun ByteSequence.deserializeTo(clazz: Class, classLoader: SandboxClassLoader): Any { + val sandboxClazz = classLoader.toSandboxClass(clazz) + return deserializeTo(sandboxClazz) +} + +fun ByteSequence.deserializeTo(clazz: Class<*>): Any { + return deserializeTo(clazz, SerializationFactory.defaultFactory) +} + +fun ByteSequence.deserializeTo(clazz: Class<*>, factory: SerializationFactory): Any { + return factory.deserialize(this, clazz, factory.defaultContext) +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/ExceptionUtils.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/ExceptionUtils.kt new file mode 100644 index 0000000000..edb859d3bd --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/ExceptionUtils.kt @@ -0,0 +1,39 @@ +@file:JvmName("ExceptionUtils") +package net.corda.serialization.djvm.serializers + +import net.corda.serialization.internal.amqp.AMQPNotSerializableException + +/** + * Utility function which helps tracking the path in the object graph when exceptions are thrown. + * Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue. + * Path information is added to the message of the exception being thrown. + */ +@Suppress("TooGenericExceptionCaught") +internal inline fun ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T { + try { + return block() + } catch (th: Throwable) { + when (th) { + is AMQPNotSerializableException -> th.classHierarchy.add(strToAppendFn()) + // Do not overwrite the message of these exceptions as it may be used. + is ClassNotFoundException -> {} + is NoClassDefFoundError -> {} + else -> th.resetMessage("${strToAppendFn()} -> ${th.message}") + } + throw th + } +} + +/** + * Not a public property so will have to use reflection + */ +private fun Throwable.resetMessage(newMsg: String) { + val detailMessageField = Throwable::class.java.getDeclaredField("detailMessage") + detailMessageField.isAccessible = true + detailMessageField.set(this, newMsg) +} + +/** + * We currently only support deserialisation, and so we're going to need this. + */ +fun abortReadOnly(): Nothing = throw UnsupportedOperationException("Read Only!") \ No newline at end of file diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/PrimitiveSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/PrimitiveSerializer.kt new file mode 100644 index 0000000000..52e9cd847c --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/PrimitiveSerializer.kt @@ -0,0 +1,36 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.serialization.internal.amqp.AMQPSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import net.corda.serialization.internal.amqp.typeDescriptorFor +import org.apache.qpid.proton.amqp.Binary +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class PrimitiveSerializer( + override val type: Class<*>, + private val sandboxBasicInput: Function +) : AMQPSerializer { + override val typeDescriptor: Symbol = typeDescriptorFor(type) + + override fun readObject( + obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext + ): Any { + return (obj as? Binary)?.array ?: sandboxBasicInput.apply(obj)!! + } + + override fun writeClassInfo(output: SerializationOutput) { + abortReadOnly() + } + + override fun writeObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int + ) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxBitSetSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxBitSetSerializer.kt new file mode 100644 index 0000000000..f56ff31706 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxBitSetSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.BitSetDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.BitSetSerializer.BitSetProxy +import java.util.BitSet +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxBitSetSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(BitSet::class.java), + proxyClass = classLoader.toSandboxAnyClass(BitSetProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, BitSetDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(BitSet::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCertPathSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCertPathSerializer.kt new file mode 100644 index 0000000000..f9351420c2 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCertPathSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.CertPathDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.CertPathSerializer.CertPathProxy +import java.security.cert.CertPath +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxCertPathSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(CertPath::class.java), + proxyClass = classLoader.toSandboxAnyClass(CertPathProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, CertPathDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(CertPath::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCharacterSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCharacterSerializer.kt new file mode 100644 index 0000000000..926982a82b --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCharacterSerializer.kt @@ -0,0 +1,37 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxCharacterSerializer( + classLoader: SandboxClassLoader, + private val basicInput: Function +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(Char::class.javaObjectType)) { + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return basicInput.apply(convertToChar(obj))!! + } + + private fun convertToChar(obj: Any): Any { + return when (obj) { + is Short -> obj.toChar() + is Int -> obj.toChar() + else -> obj + } + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxClassSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxClassSerializer.kt new file mode 100644 index 0000000000..7f2d0abf02 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxClassSerializer.kt @@ -0,0 +1,47 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.ClassDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.AMQPNotSerializableException +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy +import java.util.function.Function + +@Suppress("unchecked_cast") +class SandboxClassSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = Class::class.java as Class, + proxyClass = classLoader.toSandboxAnyClass(ClassProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, ClassDeserializer::class.java) + private val nameOf: Function + + init { + val fetch = proxyClass.getMethod("getClassName") + nameOf = Function { proxy -> + fetch(proxy).toString() + } + } + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return try { + task.apply(proxy)!! + } catch (e: ClassNotFoundException) { + val className = nameOf.apply(proxy) + throw AMQPNotSerializableException(type, + "Could not instantiate $className - not on the classpath", + "$className was not found by the node, check the Node containing the CorDapp that " + + "implements $className is loaded and on the Classpath", + mutableListOf(className) + ) + } + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCollectionSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCollectionSerializer.kt new file mode 100644 index 0000000000..0f2f53c6f2 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCollectionSerializer.kt @@ -0,0 +1,129 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.core.utilities.NonEmptySet +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.CreateCollection +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.AMQPSerializer +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.LocalSerializerFactory +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import net.corda.serialization.internal.amqp.redescribe +import net.corda.serialization.internal.model.LocalTypeInformation +import net.corda.serialization.internal.model.TypeIdentifier +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.EnumSet +import java.util.NavigableSet +import java.util.SortedSet +import java.util.function.Function + +class SandboxCollectionSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + private val localFactory: LocalSerializerFactory +) : CustomSerializer.Implements(clazz = classLoader.toSandboxAnyClass(Collection::class.java)) { + @Suppress("unchecked_cast") + private val creator: Function, out Any?> + = classLoader.createTaskFor(taskFactory, CreateCollection::class.java) as Function, out Any?> + + private val unsupportedTypes: Set> = listOf( + EnumSet::class.java + ).map { + classLoader.toSandboxAnyClass(it) + }.toSet() + + // The order matters here - the first match should be the most specific one. + // Kotlin preserves the ordering for us by associating into a LinkedHashMap. + private val supportedTypes: Map, Class>> = listOf( + List::class.java, + NonEmptySet::class.java, + NavigableSet::class.java, + SortedSet::class.java, + Set::class.java, + Collection::class.java + ).associateBy { + classLoader.toSandboxAnyClass(it) + } + + private fun getBestMatchFor(type: Class): Map.Entry, Class>> + = supportedTypes.entries.first { it.key.isAssignableFrom(type) } + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun isSerializerFor(clazz: Class<*>): Boolean { + return super.isSerializerFor(clazz) && unsupportedTypes.none { it.isAssignableFrom(clazz) } + } + + override fun specialiseFor(declaredType: Type): AMQPSerializer? { + if (declaredType !is ParameterizedType) { + return null + } + + @Suppress("unchecked_cast") + val rawType = declaredType.rawType as Class + return ConcreteCollectionSerializer(declaredType, getBestMatchFor(rawType), creator, localFactory) + } + + override fun readObject( + obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext + ): Any { + throw UnsupportedOperationException("Factory only") + } + + override fun writeDescribedObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext + ) { + throw UnsupportedOperationException("Factory Only") + } +} + +private class ConcreteCollectionSerializer( + declaredType: ParameterizedType, + private val matchingType: Map.Entry, Class>>, + private val creator: Function, out Any?>, + factory: LocalSerializerFactory +) : AMQPSerializer { + override val type: ParameterizedType = declaredType + + override val typeDescriptor: Symbol by lazy { + factory.createDescriptor( + LocalTypeInformation.ACollection( + observedType = declaredType.rawType, + typeIdentifier = TypeIdentifier.forGenericType(declaredType), + elementType =factory.getTypeInformation(declaredType.actualTypeArguments[0]) + ) + ) + } + + override fun readObject( + obj: Any, + schemas: SerializationSchemas, + input: DeserializationInput, + context: SerializationContext + ): Any { + val inboundType = type.actualTypeArguments[0] + return ifThrowsAppend({ type.typeName }) { + val args = (obj as List<*>).map { + input.readObjectOrNull(redescribe(it, inboundType), schemas, inboundType, context) + }.toTypedArray() + creator.apply(arrayOf(matchingType.key, args))!! + } + } + + override fun writeClassInfo(output: SerializationOutput) { + abortReadOnly() + } + + override fun writeObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int + ) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCurrencySerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCurrencySerializer.kt new file mode 100644 index 0000000000..e49e62d144 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxCurrencySerializer.kt @@ -0,0 +1,41 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.CreateCurrency +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.Collections.singleton +import java.util.Currency +import java.util.function.Function + +class SandboxCurrencySerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + basicInput: Function +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(Currency::class.java)) { + private val creator: Function + + init { + val createTask = classLoader.createTaskFor(taskFactory, CreateCurrency::class.java) + creator = basicInput.andThen(createTask) + } + + override val deserializationAliases: Set> = singleton(Currency::class.java) + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return creator.apply(obj)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal128Serializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal128Serializer.kt new file mode 100644 index 0000000000..d7e2e0d248 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal128Serializer.kt @@ -0,0 +1,35 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.Decimal128Deserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.Decimal128 +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxDecimal128Serializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(Decimal128::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, Decimal128Deserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + val decimal128 = obj as Decimal128 + return transformer.apply(longArrayOf(decimal128.mostSignificantBits, decimal128.leastSignificantBits))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal32Serializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal32Serializer.kt new file mode 100644 index 0000000000..4b83c30a9a --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal32Serializer.kt @@ -0,0 +1,34 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.Decimal32Deserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.Decimal32 +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxDecimal32Serializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(Decimal32::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, Decimal32Deserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply(intArrayOf((obj as Decimal32).bits))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal64Serializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal64Serializer.kt new file mode 100644 index 0000000000..6103d8431b --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDecimal64Serializer.kt @@ -0,0 +1,34 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.Decimal64Deserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.Decimal64 +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxDecimal64Serializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(Decimal64::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, Decimal64Deserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply(longArrayOf((obj as Decimal64).bits))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDurationSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDurationSerializer.kt new file mode 100644 index 0000000000..8432f5cec2 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxDurationSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.DurationDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.DurationSerializer.DurationProxy +import java.time.Duration +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxDurationSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(Duration::class.java), + proxyClass = classLoader.toSandboxAnyClass(DurationProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, DurationDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(Duration::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt new file mode 100644 index 0000000000..859e3e011c --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSerializer.kt @@ -0,0 +1,103 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.DescribeEnum +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.AMQPNotSerializableException +import net.corda.serialization.internal.amqp.AMQPSerializer +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.LocalSerializerFactory +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import net.corda.serialization.internal.model.EnumTransforms +import net.corda.serialization.internal.model.LocalTypeInformation +import net.corda.serialization.internal.model.TypeIdentifier +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxEnumSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + private val localFactory: LocalSerializerFactory +) : CustomSerializer.Implements(clazz = classLoader.toSandboxAnyClass(Enum::class.java)) { + @Suppress("unchecked_cast") + private val describer: Function, Array> + = classLoader.createTaskFor(taskFactory, DescribeEnum::class.java) as Function, Array> + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun specialiseFor(declaredType: Type): AMQPSerializer? { + if (declaredType !is Class<*>) { + return null + } + val members = describer.apply(declaredType) + return ConcreteEnumSerializer(declaredType, members, localFactory) + } + + override fun readObject( + obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext + ): Any { + throw UnsupportedOperationException("Factory only") + } + + override fun writeDescribedObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext + ) { + throw UnsupportedOperationException("Factory Only") + } +} + +private class ConcreteEnumSerializer( + declaredType: Class<*>, + private val members: Array, + factory: LocalSerializerFactory +) : AMQPSerializer { + override val type: Class<*> = declaredType + + override val typeDescriptor: Symbol by lazy { + factory.createDescriptor( + /* + * Partially populated, providing just the information + * required by the fingerprinter. + */ + LocalTypeInformation.AnEnum( + declaredType, + TypeIdentifier.forGenericType(declaredType), + members.map { it.toString() }, + emptyList(), + EnumTransforms.empty + ) + ) + } + + override fun readObject( + obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext + ): Any { + val enumName = (obj as List<*>)[0] as String + val enumOrd = obj[1] as Int + val fromOrd = members[enumOrd] + + if (enumName != fromOrd.toString()) { + throw AMQPNotSerializableException( + type, + "Deserializing obj as enum $type with value $enumName.$enumOrd but ordinality has changed" + ) + } + return fromOrd + } + + override fun writeClassInfo(output: SerializationOutput) { + abortReadOnly() + } + + override fun writeObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int + ) { + abortReadOnly() + } +} \ No newline at end of file diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSetSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSetSerializer.kt new file mode 100644 index 0000000000..b729edf70f --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxEnumSetSerializer.kt @@ -0,0 +1,35 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.EnumSetDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.EnumSetSerializer.EnumSetProxy +import java.util.Collections.singleton +import java.util.EnumSet +import java.util.function.Function + +class SandboxEnumSetSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(EnumSet::class.java), + proxyClass = classLoader.toSandboxAnyClass(EnumSetProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, EnumSetDeserializer::class.java) + + override val additionalSerializers: Set> = singleton( + SandboxClassSerializer(classLoader, taskFactory, factory) + ) + + override val deserializationAliases: Set> = singleton(EnumSet::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxInputStreamSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxInputStreamSerializer.kt new file mode 100644 index 0000000000..31ea253e40 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxInputStreamSerializer.kt @@ -0,0 +1,38 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.InputStreamDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.io.InputStream +import java.lang.reflect.Type +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxInputStreamSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Implements(classLoader.toSandboxAnyClass(InputStream::class.java)) { + @Suppress("unchecked_cast") + private val decoder: Function + = classLoader.createTaskFor(taskFactory, InputStreamDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override val deserializationAliases: Set> = singleton(InputStream::class.java) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray + return decoder.apply(bits)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxInstantSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxInstantSerializer.kt new file mode 100644 index 0000000000..5491472ca2 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxInstantSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.InstantDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.InstantSerializer.InstantProxy +import java.time.Instant +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxInstantSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(Instant::class.java), + proxyClass = classLoader.toSandboxAnyClass(InstantProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, InstantDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(Instant::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalDateSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalDateSerializer.kt new file mode 100644 index 0000000000..df16065f2b --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalDateSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.LocalDateDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.LocalDateSerializer.LocalDateProxy +import java.time.LocalDate +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxLocalDateSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(LocalDate::class.java), + proxyClass = classLoader.toSandboxAnyClass(LocalDateProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, LocalDateDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(LocalDate::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalDateTimeSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalDateTimeSerializer.kt new file mode 100644 index 0000000000..235c8221f8 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalDateTimeSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.LocalDateTimeDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.LocalDateTimeSerializer.LocalDateTimeProxy +import java.time.LocalDateTime +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxLocalDateTimeSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(LocalDateTime::class.java), + proxyClass = classLoader.toSandboxAnyClass(LocalDateTimeProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, LocalDateTimeDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(LocalDateTime::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalTimeSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalTimeSerializer.kt new file mode 100644 index 0000000000..47da92d723 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxLocalTimeSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.LocalTimeDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.LocalTimeSerializer.LocalTimeProxy +import java.time.LocalTime +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxLocalTimeSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(LocalTime::class.java), + proxyClass = classLoader.toSandboxAnyClass(LocalTimeProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, LocalTimeDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(LocalTime::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxMapSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxMapSerializer.kt new file mode 100644 index 0000000000..596118a24b --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxMapSerializer.kt @@ -0,0 +1,124 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.CreateMap +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.AMQPSerializer +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.LocalSerializerFactory +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import net.corda.serialization.internal.amqp.redescribe +import net.corda.serialization.internal.model.LocalTypeInformation +import net.corda.serialization.internal.model.TypeIdentifier +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.EnumMap +import java.util.NavigableMap +import java.util.SortedMap +import java.util.TreeMap +import java.util.function.Function + +class SandboxMapSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + private val localFactory: LocalSerializerFactory +) : CustomSerializer.Implements(clazz = classLoader.toSandboxAnyClass(Map::class.java)) { + @Suppress("unchecked_cast") + private val creator: Function, out Any?> + = classLoader.createTaskFor(taskFactory, CreateMap::class.java) as Function, out Any?> + + // The order matters here - the first match should be the most specific one. + // Kotlin preserves the ordering for us by associating into a LinkedHashMap. + private val supportedTypes: Map, Class>> = listOf( + TreeMap::class.java, + LinkedHashMap::class.java, + NavigableMap::class.java, + SortedMap::class.java, + EnumMap::class.java, + Map::class.java + ).associateBy { + classLoader.toSandboxAnyClass(it) + } + + private fun getBestMatchFor(type: Class): Map.Entry, Class>> + = supportedTypes.entries.first { it.key.isAssignableFrom(type) } + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun specialiseFor(declaredType: Type): AMQPSerializer? { + if (declaredType !is ParameterizedType) { + return null + } + + @Suppress("unchecked_cast") + val rawType = declaredType.rawType as Class + return ConcreteMapSerializer(declaredType, getBestMatchFor(rawType), creator, localFactory) + } + + override fun readObject( + obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext + ): Any { + throw UnsupportedOperationException("Factory only") + } + + override fun writeDescribedObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext + ) { + throw UnsupportedOperationException("Factory Only") + } +} + +private class ConcreteMapSerializer( + declaredType: ParameterizedType, + private val matchingType: Map.Entry, Class>>, + private val creator: Function, out Any?>, + factory: LocalSerializerFactory +) : AMQPSerializer { + override val type: ParameterizedType = declaredType + + override val typeDescriptor: Symbol by lazy { + factory.createDescriptor( + LocalTypeInformation.AMap( + observedType = declaredType.rawType, + typeIdentifier = TypeIdentifier.forGenericType(declaredType), + keyType =factory.getTypeInformation(declaredType.actualTypeArguments[0]), + valueType = factory.getTypeInformation(declaredType.actualTypeArguments[1]) + ) + ) + } + + override fun readObject( + obj: Any, + schemas: SerializationSchemas, + input: DeserializationInput, + context: SerializationContext + ): Any { + val inboundKeyType = type.actualTypeArguments[0] + val inboundValueType = type.actualTypeArguments[1] + return ifThrowsAppend({ type.typeName }) { + val entries = (obj as Map<*, *>).map { + arrayOf( + input.readObjectOrNull(redescribe(it.key, inboundKeyType), schemas, inboundKeyType, context), + input.readObjectOrNull(redescribe(it.value, inboundValueType), schemas, inboundValueType, context) + ) + }.toTypedArray() + creator.apply(arrayOf(matchingType.key, entries))!! + } + } + + override fun writeClassInfo(output: SerializationOutput) { + abortReadOnly() + } + + override fun writeObject( + obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int + ) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxMonthDaySerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxMonthDaySerializer.kt new file mode 100644 index 0000000000..7066dfeb50 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxMonthDaySerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.MonthDayDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.MonthDaySerializer.MonthDayProxy +import java.time.MonthDay +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxMonthDaySerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(MonthDay::class.java), + proxyClass = classLoader.toSandboxAnyClass(MonthDayProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, MonthDayDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(MonthDay::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOffsetDateTimeSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOffsetDateTimeSerializer.kt new file mode 100644 index 0000000000..00c7833f78 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOffsetDateTimeSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.OffsetDateTimeDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer.OffsetDateTimeProxy +import java.time.OffsetDateTime +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxOffsetDateTimeSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(OffsetDateTime::class.java), + proxyClass = classLoader.toSandboxAnyClass(OffsetDateTimeProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, OffsetDateTimeDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(OffsetDateTime::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOffsetTimeSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOffsetTimeSerializer.kt new file mode 100644 index 0000000000..aac885afdb --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOffsetTimeSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.OffsetTimeDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer.OffsetTimeProxy +import java.time.OffsetTime +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxOffsetTimeSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(OffsetTime::class.java), + proxyClass = classLoader.toSandboxAnyClass(OffsetTimeProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, OffsetTimeDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(OffsetTime::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt new file mode 100644 index 0000000000..f1b0ff2d9c --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.OpaqueBytesSubSequence +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.OpaqueBytesSubSequenceDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxOpaqueBytesSubSequenceSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(OpaqueBytesSubSequence::class.java), + proxyClass = classLoader.toSandboxAnyClass(OpaqueBytes::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, OpaqueBytesSubSequenceDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(OpaqueBytesSubSequence::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOptionalSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOptionalSerializer.kt new file mode 100644 index 0000000000..f355673d74 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxOptionalSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.OptionalDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.OptionalSerializer.OptionalProxy +import java.util.Collections.singleton +import java.util.Optional +import java.util.function.Function + +class SandboxOptionalSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(Optional::class.java), + proxyClass = classLoader.toSandboxAnyClass(OptionalProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, OptionalDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(Optional::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPeriodSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPeriodSerializer.kt new file mode 100644 index 0000000000..ff3c6d7622 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPeriodSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.PeriodDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.PeriodSerializer.PeriodProxy +import java.time.Period +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxPeriodSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(Period::class.java), + proxyClass = classLoader.toSandboxAnyClass(PeriodProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, PeriodDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(Period::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPrimitiveSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPrimitiveSerializer.kt new file mode 100644 index 0000000000..9af70c9d4a --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPrimitiveSerializer.kt @@ -0,0 +1,30 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxPrimitiveSerializer( + clazz: Class<*>, + classLoader: SandboxClassLoader, + private val basicInput: Function +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(clazz)) { + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return basicInput.apply(obj)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt new file mode 100644 index 0000000000..2c828774f2 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt @@ -0,0 +1,38 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.PublicKeyDecoder +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.security.PublicKey +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxPublicKeySerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Implements(classLoader.toSandboxAnyClass(PublicKey::class.java)) { + @Suppress("unchecked_cast") + private val decoder: Function + = classLoader.createTaskFor(taskFactory, PublicKeyDecoder::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override val deserializationAliases: Set> = singleton(PublicKey::class.java) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray + return decoder.apply(bits)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxSymbolSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxSymbolSerializer.kt new file mode 100644 index 0000000000..5ed9719337 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxSymbolSerializer.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.SymbolDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxSymbolSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + basicInput: Function +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(Symbol::class.java)) { + private val transformer: Function + + init { + val transformTask = classLoader.createTaskFor(taskFactory, SymbolDeserializer::class.java) + @Suppress("unchecked_cast") + transformer = basicInput.andThen(transformTask) as Function + } + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply((obj as Symbol).toString())!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxToStringSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxToStringSerializer.kt new file mode 100644 index 0000000000..9149cdff7b --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxToStringSerializer.kt @@ -0,0 +1,45 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.CreateFromString +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Constructor +import java.lang.reflect.Type +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxToStringSerializer( + unsafeClass: Class<*>, + classLoader: SandboxClassLoader, + taskFactory: Function>, + basicInput: Function +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(unsafeClass)) { + private val creator: Function + + init { + val stringClass = classLoader.loadClass("sandbox.java.lang.String") + val createTask = classLoader.toSandboxClass(CreateFromString::class.java) + .getConstructor(Constructor::class.java) + .newInstance(clazz.getConstructor(stringClass)) + creator = basicInput.andThen(taskFactory.apply(createTask)) + } + + override val deserializationAliases: Set> = singleton(unsafeClass) + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return creator.apply(obj)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedByteSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedByteSerializer.kt new file mode 100644 index 0000000000..1eb3f67409 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedByteSerializer.kt @@ -0,0 +1,34 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.UnsignedByteDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.UnsignedByte +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxUnsignedByteSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(UnsignedByte::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, UnsignedByteDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply(byteArrayOf((obj as UnsignedByte).toByte()))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedIntegerSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedIntegerSerializer.kt new file mode 100644 index 0000000000..38ee46b9b8 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedIntegerSerializer.kt @@ -0,0 +1,34 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.UnsignedIntegerDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.UnsignedInteger +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxUnsignedIntegerSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(UnsignedInteger::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, UnsignedIntegerDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply(intArrayOf((obj as UnsignedInteger).toInt()))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedLongSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedLongSerializer.kt new file mode 100644 index 0000000000..9e06c59ca8 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedLongSerializer.kt @@ -0,0 +1,34 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.UnsignedLongDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.UnsignedLong +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxUnsignedLongSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(UnsignedLong::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, UnsignedLongDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply(longArrayOf((obj as UnsignedLong).toLong()))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} \ No newline at end of file diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedShortSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedShortSerializer.kt new file mode 100644 index 0000000000..b22ff1cb8c --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxUnsignedShortSerializer.kt @@ -0,0 +1,34 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.UnsignedShortDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.amqp.UnsignedShort +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.util.function.Function + +class SandboxUnsignedShortSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Is(classLoader.toSandboxAnyClass(UnsignedShort::class.java)) { + @Suppress("unchecked_cast") + private val transformer: Function + = classLoader.createTaskFor(taskFactory, UnsignedShortDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + return transformer.apply(shortArrayOf((obj as UnsignedShort).toShort()))!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxX509CRLSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxX509CRLSerializer.kt new file mode 100644 index 0000000000..1cde0cb318 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxX509CRLSerializer.kt @@ -0,0 +1,38 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.X509CRLDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.security.cert.X509CRL +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxX509CRLSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Implements(classLoader.toSandboxAnyClass(X509CRL::class.java)) { + @Suppress("unchecked_cast") + private val generator: Function + = classLoader.createTaskFor(taskFactory, X509CRLDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override val deserializationAliases: Set> = singleton(X509CRL::class.java) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray + return generator.apply(bits)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxX509CertificateSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxX509CertificateSerializer.kt new file mode 100644 index 0000000000..ab6073a9d6 --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxX509CertificateSerializer.kt @@ -0,0 +1,38 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.X509CertificateDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Schema +import net.corda.serialization.internal.amqp.SerializationOutput +import net.corda.serialization.internal.amqp.SerializationSchemas +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.security.cert.X509Certificate +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxX509CertificateSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function> +) : CustomSerializer.Implements(classLoader.toSandboxAnyClass(X509Certificate::class.java)) { + @Suppress("unchecked_cast") + private val generator: Function + = classLoader.createTaskFor(taskFactory, X509CertificateDeserializer::class.java) as Function + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override val deserializationAliases: Set> = singleton(X509Certificate::class.java) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray + return generator.apply(bits)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxYearMonthSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxYearMonthSerializer.kt new file mode 100644 index 0000000000..cbfdfd010a --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxYearMonthSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.YearMonthDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.YearMonthSerializer.YearMonthProxy +import java.time.YearMonth +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxYearMonthSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(YearMonth::class.java), + proxyClass = classLoader.toSandboxAnyClass(YearMonthProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, YearMonthDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(YearMonth::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxYearSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxYearSerializer.kt new file mode 100644 index 0000000000..4ae0e0451e --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxYearSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.YearDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.YearSerializer.YearProxy +import java.time.Year +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxYearSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(Year::class.java), + proxyClass = classLoader.toSandboxAnyClass(YearProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, YearDeserializer::class.java) + + override val deserializationAliases: Set> = singleton(Year::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxZoneIdSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxZoneIdSerializer.kt new file mode 100644 index 0000000000..2a0a36a83c --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxZoneIdSerializer.kt @@ -0,0 +1,33 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.ZoneIdDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.ZoneIdSerializer.ZoneIdProxy +import java.time.ZoneId +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxZoneIdSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(ZoneId::class.java), + proxyClass = classLoader.toSandboxAnyClass(ZoneIdProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, ZoneIdDeserializer::class.java) + + override val revealSubclassesInSchema: Boolean = true + + override val deserializationAliases: Set> = singleton(ZoneId::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return task.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxZonedDateTimeSerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxZonedDateTimeSerializer.kt new file mode 100644 index 0000000000..d22819ff5b --- /dev/null +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxZonedDateTimeSerializer.kt @@ -0,0 +1,48 @@ +package net.corda.serialization.djvm.serializers + +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.serialization.djvm.deserializers.ZonedDateTimeDeserializer +import net.corda.serialization.djvm.toSandboxAnyClass +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.custom.ZonedDateTimeSerializer.ZonedDateTimeProxy +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.util.Collections.singleton +import java.util.function.Function + +class SandboxZonedDateTimeSerializer( + classLoader: SandboxClassLoader, + taskFactory: Function>, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.toSandboxAnyClass(ZonedDateTime::class.java), + proxyClass = classLoader.toSandboxAnyClass(ZonedDateTimeProxy::class.java), + factory = factory +) { + private val task = classLoader.createTaskFor(taskFactory, ZonedDateTimeDeserializer::class.java) + private val creator: Function + + init { + val createTask = clazz.getMethod( + "createDJVM", + classLoader.toSandboxClass(LocalDateTime::class.java), + classLoader.toSandboxClass(ZoneOffset::class.java), + classLoader.toSandboxClass(ZoneId::class.java) + ) + creator = task.andThen { input -> + @Suppress("unchecked_cast", "SpreadOperator") + createTask(null, *(input as Array))!! + } + } + + override val deserializationAliases: Set> = singleton(ZonedDateTime::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return creator.apply(proxy)!! + } +} diff --git a/serialization-djvm/src/test/java/greymalkin/ExternalData.java b/serialization-djvm/src/test/java/greymalkin/ExternalData.java new file mode 100644 index 0000000000..8774478bdb --- /dev/null +++ b/serialization-djvm/src/test/java/greymalkin/ExternalData.java @@ -0,0 +1,13 @@ +package greymalkin; + +public class ExternalData { + private final String data; + + public ExternalData(String data) { + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/serialization-djvm/src/test/java/greymalkin/ExternalEnum.java b/serialization-djvm/src/test/java/greymalkin/ExternalEnum.java new file mode 100644 index 0000000000..2180693367 --- /dev/null +++ b/serialization-djvm/src/test/java/greymalkin/ExternalEnum.java @@ -0,0 +1,11 @@ +package greymalkin; + +import net.corda.core.serialization.CordaSerializable; + +@SuppressWarnings("unused") +@CordaSerializable +public enum ExternalEnum { + DOH, + RAY, + ME +} diff --git a/serialization-djvm/src/test/java/net/corda/serialization/djvm/InnocentData.java b/serialization-djvm/src/test/java/net/corda/serialization/djvm/InnocentData.java new file mode 100644 index 0000000000..1f350f940d --- /dev/null +++ b/serialization-djvm/src/test/java/net/corda/serialization/djvm/InnocentData.java @@ -0,0 +1,22 @@ +package net.corda.serialization.djvm; + +import net.corda.core.serialization.CordaSerializable; + +@CordaSerializable +public class InnocentData { + private final String message; + private final Short number; + + public InnocentData(String message, Short number) { + this.message = message; + this.number = number; + } + + public String getMessage() { + return message; + } + + public Short getNumber() { + return number; + } +} diff --git a/serialization-djvm/src/test/java/net/corda/serialization/djvm/MultiConstructorData.java b/serialization-djvm/src/test/java/net/corda/serialization/djvm/MultiConstructorData.java new file mode 100644 index 0000000000..9a6e66d86e --- /dev/null +++ b/serialization-djvm/src/test/java/net/corda/serialization/djvm/MultiConstructorData.java @@ -0,0 +1,53 @@ +package net.corda.serialization.djvm; + +import net.corda.core.serialization.ConstructorForDeserialization; +import net.corda.core.serialization.CordaSerializable; + +@SuppressWarnings({"unused", "WeakerAccess"}) +@CordaSerializable +public class MultiConstructorData { + private final String message; + private final long bigNumber; + private final Character tag; + + @ConstructorForDeserialization + public MultiConstructorData(String message, long bigNumber, Character tag) { + this.message = message; + this.bigNumber = bigNumber; + this.tag = tag; + } + + public MultiConstructorData(String message, long bigNumber) { + this(message, bigNumber, null); + } + + public MultiConstructorData(String message, char tag) { + this(message, 0, tag); + } + + public MultiConstructorData(String message) { + this(message, 0); + } + + public String getMessage() { + return message; + } + + public long getBigNumber() { + return bigNumber; + } + + public Character getTag() { + return tag; + } + + @SuppressWarnings("StringBufferReplaceableByString") + @Override + public String toString() { + return new StringBuilder("MultiConstructor[message='").append(message) + .append("', bigNumber=").append(bigNumber) + .append(", tag=").append(tag) + .append(']') + .toString(); + } +} diff --git a/serialization-djvm/src/test/java/net/corda/serialization/djvm/VeryEvilData.java b/serialization-djvm/src/test/java/net/corda/serialization/djvm/VeryEvilData.java new file mode 100644 index 0000000000..d1ea8f0ad0 --- /dev/null +++ b/serialization-djvm/src/test/java/net/corda/serialization/djvm/VeryEvilData.java @@ -0,0 +1,15 @@ +package net.corda.serialization.djvm; + +@SuppressWarnings("unused") +public class VeryEvilData extends InnocentData { + static { + if (!VeryEvilData.class.getName().startsWith("sandbox.")) { + // Execute our evil payload OUTSIDE the sandbox! + throw new IllegalStateException("Victory is mine!"); + } + } + + public VeryEvilData(String message, Short number) { + super(message, number); + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBigDecimalTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBigDecimalTest.kt new file mode 100644 index 0000000000..cc71c03fd7 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBigDecimalTest.kt @@ -0,0 +1,49 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.math.BigDecimal +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeBigDecimalTest : TestBase(KOTLIN) { + companion object { + const val VERY_BIG_DECIMAL = 994349993939.32737232 + } + + @Test + fun `test deserializing big decimal`() { + val bigDecimal = BigDecimalData(BigDecimal.valueOf(VERY_BIG_DECIMAL)) + val data = bigDecimal.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxBigInteger = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showBigDecimal = classLoader.createTaskFor(taskFactory, ShowBigDecimal::class.java) + val result = showBigDecimal.apply(sandboxBigInteger) ?: fail("Result cannot be null") + + assertEquals(ShowBigDecimal().apply(bigDecimal), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowBigDecimal : Function { + override fun apply(data: BigDecimalData): String { + return with(data) { + "BigDecimal: $number" + } + } + } +} + +@CordaSerializable +data class BigDecimalData(val number: BigDecimal) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBigIntegerTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBigIntegerTest.kt new file mode 100644 index 0000000000..50b7bd3691 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBigIntegerTest.kt @@ -0,0 +1,49 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.math.BigInteger +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeBigIntegerTest : TestBase(KOTLIN) { + companion object { + const val VERY_BIG_NUMBER = 1234567890123456789 + } + + @Test + fun `test deserializing big integer`() { + val bigInteger = BigIntegerData(BigInteger.valueOf(VERY_BIG_NUMBER)) + val data = bigInteger.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxBigInteger = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showBigInteger = classLoader.createTaskFor(taskFactory, ShowBigInteger::class.java) + val result = showBigInteger.apply(sandboxBigInteger) ?: fail("Result cannot be null") + + assertEquals(ShowBigInteger().apply(bigInteger), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowBigInteger : Function { + override fun apply(data: BigIntegerData): String { + return with(data) { + "BigInteger: $number" + } + } + } +} + +@CordaSerializable +data class BigIntegerData(val number: BigInteger) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBitSetTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBitSetTest.kt new file mode 100644 index 0000000000..9c09ce2847 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeBitSetTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.BitSet +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeBitSetTest : TestBase(KOTLIN) { + @Test + fun `test deserializing bitset`() { + val bitSet = BitSet.valueOf(byteArrayOf(0x00, 0x70, 0x55, 0x3A, 0x48, 0x12)) + val data = bitSet.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxBitSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showBitSet = classLoader.createTaskFor(taskFactory, ShowBitSet::class.java) + val result = showBitSet.apply(sandboxBitSet) ?: fail("Result cannot be null") + + assertEquals(bitSet.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowBitSet : Function { + override fun apply(bitSet: BitSet): String { + return bitSet.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCertificatesTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCertificatesTest.kt new file mode 100644 index 0000000000..897c13828e --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCertificatesTest.kt @@ -0,0 +1,129 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.assertj.core.api.Assertions.assertThat +import org.bouncycastle.asn1.x509.CRLReason +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509v2CRLBuilder +import org.bouncycastle.cert.jcajce.JcaX509CRLConverter +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.security.KeyPairGenerator +import java.security.cert.CertPath +import java.security.cert.CertificateFactory +import java.security.cert.X509CRL +import java.security.cert.X509Certificate +import java.util.Date +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeCertificatesTest : TestBase(KOTLIN) { + companion object { + // The sandbox's localisation may not match that of the host. + // E.g. line separator characters. + fun String.toUNIX(): String { + return replace(System.lineSeparator(), "\n") + } + + val factory: CertificateFactory = CertificateFactory.getInstance("X.509") + lateinit var certificate: X509Certificate + + @Suppress("unused") + @BeforeAll + @JvmStatic + fun loadCertificate() { + certificate = this::class.java.classLoader.getResourceAsStream("testing.cert")?.use { input -> + factory.generateCertificate(input) as X509Certificate + } ?: fail("Certificate not found") + } + } + + @Test + fun `test deserialize certificate path`() { + val certPath = factory.generateCertPath(listOf(certificate)) + val data = certPath.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxCertPath = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showCertPath = classLoader.createTaskFor(taskFactory, ShowCertPath::class.java) + val result = showCertPath.apply(sandboxCertPath) ?: fail("Result cannot be null") + + assertEquals(ShowCertPath().apply(certPath).toUNIX(), result.toString()) + assertThat(result::class.java.name).startsWith("sandbox.") + } + } + + class ShowCertPath : Function { + override fun apply(certPath: CertPath): String { + return "CertPath -> $certPath" + } + } + + @Test + fun `test deserialize X509 certificate`() { + val data = certificate.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxCertificate = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showCertificate = classLoader.createTaskFor(taskFactory, ShowCertificate::class.java) + val result = showCertificate.apply(sandboxCertificate) ?: fail("Result cannot be null") + + assertEquals(ShowCertificate().apply(certificate).toUNIX(), result.toString()) + assertThat(result::class.java.name).startsWith("sandbox.") + } + } + + class ShowCertificate : Function { + override fun apply(certificate: X509Certificate): String { + return "X.509 Certificate -> $certificate" + } + } + + @Test + fun `test X509 CRL`() { + val caKeyPair = KeyPairGenerator.getInstance("RSA") + .generateKeyPair() + val signer = JcaContentSignerBuilder("SHA256WithRSAEncryption") + .build(caKeyPair.private) + + val now = Date() + val crl = with(X509v2CRLBuilder(X500Name("CN=Test CA"), now)) { + addCRLEntry(certificate.serialNumber, now, CRLReason.privilegeWithdrawn) + JcaX509CRLConverter().getCRL(build(signer)) + } + val data = crl.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxCRL = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showCRL = classLoader.createTaskFor(taskFactory, ShowCRL::class.java) + val result = showCRL.apply(sandboxCRL) ?: fail("Result cannot be null") + + assertEquals(ShowCRL().apply(crl).toUNIX(), result.toString()) + assertThat(result::class.java.name).startsWith("sandbox.") + } + } + + class ShowCRL : Function { + override fun apply(crl: X509CRL): String { + return "X.509 CRL -> $crl" + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeClassTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeClassTest.kt new file mode 100644 index 0000000000..080360a850 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeClassTest.kt @@ -0,0 +1,59 @@ +package net.corda.serialization.djvm + +import greymalkin.ExternalData +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.djvm.messages.Severity +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.io.NotSerializableException +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeClassTest : TestBase(KOTLIN) { + @Test + fun `test deserializing existing class`() { + val myClass = ExternalData::class.java + val data = myClass.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxInstant = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showClass = classLoader.createTaskFor(taskFactory, ShowClass::class.java) + val result = showClass.apply(sandboxInstant) ?: fail("Result cannot be null") + + assertEquals("sandbox.${myClass.name}", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + @Test + fun `test deserializing missing class`() { + // The DJVM will refuse to find this class because it belongs to net.corda.djvm.**. + val myClass = Severity::class.java + val data = myClass.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val ex = assertThrows{ data.deserializeFor(classLoader) } + assertThat(ex) + .isExactlyInstanceOf(NotSerializableException::class.java) + .hasMessageContaining("Severity was not found by the node,") + } + } +} + +class ShowClass : Function, String> { + override fun apply(type: Class<*>): String { + return type.name + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCollectionsTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCollectionsTest.kt new file mode 100644 index 0000000000..7b328c1d8c --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCollectionsTest.kt @@ -0,0 +1,215 @@ +package net.corda.serialization.djvm + +import greymalkin.ExternalEnum +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.core.utilities.NonEmptySet +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.EnumSet +import java.util.NavigableSet +import java.util.SortedSet +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeCollectionsTest : TestBase(KOTLIN) { + @Test + fun `test deserializing string list`() { + val stringList = StringList(listOf("Hello", "World", "!")) + val data = stringList.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxList = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringList = classLoader.createTaskFor(taskFactory, ShowStringList::class.java) + val result = showStringList.apply(sandboxList) ?: fail("Result cannot be null") + + assertEquals(stringList.lines.joinToString(), result.toString()) + assertEquals("Hello, World, !", result.toString()) + } + } + + class ShowStringList : Function { + override fun apply(data: StringList): String { + return data.lines.joinToString() + } + } + + @Test + fun `test deserializing integer set`() { + val integerSet = IntegerSet(linkedSetOf(10, 3, 15, 2, 10)) + val data = integerSet.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showIntegerSet = classLoader.createTaskFor(taskFactory, ShowIntegerSet::class.java) + val result = showIntegerSet.apply(sandboxSet) ?: fail("Result cannot be null") + + assertEquals(integerSet.numbers.joinToString(), result.toString()) + assertEquals("10, 3, 15, 2", result.toString()) + } + } + + class ShowIntegerSet : Function { + override fun apply(data: IntegerSet): String { + return data.numbers.joinToString() + } + } + + @Test + fun `test deserializing integer sorted set`() { + val integerSortedSet = IntegerSortedSet(sortedSetOf(10, 15, 1000, 3, 2, 10)) + val data = integerSortedSet.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showIntegerSortedSet = classLoader.createTaskFor(taskFactory, ShowIntegerSortedSet::class.java) + val result = showIntegerSortedSet.apply(sandboxSet) ?: fail("Result cannot be null") + + assertEquals(integerSortedSet.numbers.joinToString(), result.toString()) + assertEquals("2, 3, 10, 15, 1000", result.toString()) + } + } + + class ShowIntegerSortedSet : Function { + override fun apply(data: IntegerSortedSet): String { + return data.numbers.joinToString() + } + } + + @Test + fun `test deserializing long navigable set`() { + val longNavigableSet = LongNavigableSet(sortedSetOf(99955L, 10, 15, 1000, 3, 2, 10)) + val data = longNavigableSet.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showLongNavigableSet = classLoader.createTaskFor(taskFactory, ShowLongNavigableSet::class.java) + val result = showLongNavigableSet.apply(sandboxSet) ?: fail("Result cannot be null") + + assertEquals(longNavigableSet.numbers.joinToString(), result.toString()) + assertEquals("2, 3, 10, 15, 1000, 99955", result.toString()) + } + } + + class ShowLongNavigableSet : Function { + override fun apply(data: LongNavigableSet): String { + return data.numbers.joinToString() + } + } + + @Test + fun `test deserializing short collection`() { + val shortCollection = ShortCollection(listOf(10, 200, 3000)) + val data = shortCollection.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxCollection = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showShortCollection = classLoader.createTaskFor(taskFactory, ShowShortCollection::class.java) + val result = showShortCollection.apply(sandboxCollection) ?: fail("Result cannot be null") + + assertEquals(shortCollection.numbers.joinToString(), result.toString()) + assertEquals("10, 200, 3000", result.toString()) + } + } + + class ShowShortCollection : Function { + override fun apply(data: ShortCollection): String { + return data.numbers.joinToString() + } + } + + @Test + fun `test deserializing non-empty string set`() { + val nonEmptyStrings = NonEmptyStringSet(NonEmptySet.of("Hello", "World", "!")) + val data = nonEmptyStrings.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showNonEmptyStringSet = classLoader.createTaskFor(taskFactory, ShowNonEmptyStringSet::class.java) + val result = showNonEmptyStringSet.apply(sandboxSet) ?: fail("Result cannot be null") + + assertEquals(nonEmptyStrings.lines.joinToString(), result.toString()) + assertEquals("Hello, World, !", result.toString()) + } + } + + class ShowNonEmptyStringSet : Function { + override fun apply(data: NonEmptyStringSet): String { + return data.lines.joinToString() + } + } + + @Test + fun `test deserializing enum set`() { + val enumSet = HasEnumSet(EnumSet.of(ExternalEnum.DOH)) + val data = enumSet.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showHasEnumSet = classLoader.createTaskFor(taskFactory, ShowHasEnumSet::class.java) + val result = showHasEnumSet.apply(sandboxSet) ?: fail("Result cannot be null") + + assertEquals(enumSet.values.toString(), result.toString()) + assertEquals("[DOH]", result.toString()) + } + } + + class ShowHasEnumSet : Function { + override fun apply(data: HasEnumSet): String { + return data.values.toString() + } + } +} + +@CordaSerializable +class StringList(val lines: List) + +@CordaSerializable +class IntegerSet(val numbers: Set) + +@CordaSerializable +class IntegerSortedSet(val numbers: SortedSet) + +@CordaSerializable +class LongNavigableSet(val numbers: NavigableSet) + +@CordaSerializable +class ShortCollection(val numbers: Collection) + +@CordaSerializable +class NonEmptyStringSet(val lines: NonEmptySet) + +@CordaSerializable +class HasEnumSet(val values: EnumSet) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCurrencyTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCurrencyTest.kt new file mode 100644 index 0000000000..e27b015da9 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeCurrencyTest.kt @@ -0,0 +1,45 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.Currency +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeCurrencyTest : TestBase(KOTLIN) { + @Test + fun `test deserializing currency`() { + val currency = CurrencyData(Currency.getInstance("GBP")) + val data = currency.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxCurrency = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showCurrency = classLoader.createTaskFor(taskFactory, ShowCurrency::class.java) + val result = showCurrency.apply(sandboxCurrency) ?: fail("Result cannot be null") + + assertEquals(ShowCurrency().apply(currency), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowCurrency : Function { + override fun apply(data: CurrencyData): String { + return with(data) { + "Currency: $currency" + } + } + } +} + +@CordaSerializable +data class CurrencyData(val currency: Currency) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeEnumSetTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeEnumSetTest.kt new file mode 100644 index 0000000000..5d8b1d6ccb --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeEnumSetTest.kt @@ -0,0 +1,43 @@ +package net.corda.serialization.djvm + +import greymalkin.ExternalEnum +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import java.util.EnumSet +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeEnumSetTest : TestBase(KOTLIN) { + @ParameterizedTest + @EnumSource(ExternalEnum::class) + fun `test deserialize enum set`(value: ExternalEnum) { + val enumSet = EnumSet.of(value) + val data = enumSet.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxEnumSet = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showEnumSet = classLoader.createTaskFor(taskFactory, ShowEnumSet::class.java) + val result = showEnumSet.apply(sandboxEnumSet) ?: fail("Result cannot be null") + + assertEquals(ShowEnumSet().apply(enumSet), result.toString()) + assertEquals("EnumSet: [$value]'", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowEnumSet : Function, String> { + override fun apply(input: EnumSet<*>): String { + return "EnumSet: $input'" + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeEnumTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeEnumTest.kt new file mode 100644 index 0000000000..c110ea96d7 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeEnumTest.kt @@ -0,0 +1,55 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeEnumTest : TestBase(KOTLIN) { + @ParameterizedTest + @EnumSource(ExampleEnum::class) + fun `test deserialize basic enum`(value: ExampleEnum) { + val example = ExampleData(value) + val data = example.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxExample = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showExampleData = classLoader.createTaskFor(taskFactory, ShowExampleData::class.java) + val result = showExampleData.apply(sandboxExample) ?: fail("Result cannot be null") + + assertEquals(ShowExampleData().apply(example), result.toString()) + assertEquals("Example: name='${value.name}', ordinal='${value.ordinal}'", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowExampleData : Function { + override fun apply(input: ExampleData): String { + return with(input) { + "Example: name='${value.name}', ordinal='${value.ordinal}'" + } + } + } +} + +@Suppress("unused") +@CordaSerializable +enum class ExampleEnum { + ONE, + TWO, + THREE +} + +@CordaSerializable +data class ExampleData(val value: ExampleEnum) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeGenericsTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeGenericsTest.kt new file mode 100644 index 0000000000..84fce6e9fd --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeGenericsTest.kt @@ -0,0 +1,170 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeGenericsTest : TestBase(KOTLIN) { + @Test + fun `test deserializing generic wrapper with String`() { + val wrappedString = GenericWrapper(data = "Hello World!") + val data = wrappedString.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxWrapper = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val getGenericData = classLoader.createTaskFor(taskFactory, GetGenericData::class.java) + val result = getGenericData.apply(sandboxWrapper) ?: fail("Result cannot be null") + + assertEquals("Hello World!", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + @Test + fun `test deserializing generic wrapper with Integer`() { + val wrappedInteger = GenericWrapper(data = 1000) + val data = wrappedInteger.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxWrapper = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val getGenericData = classLoader.createTaskFor(taskFactory, GetGenericData::class.java) + val result = getGenericData.apply(sandboxWrapper) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals(1000, classLoader.createBasicOutput().apply(result)) + } + } + + @Test + fun `test deserializing generic wrapper with array of Integer`() { + val wrappedArrayOfInteger = GenericWrapper(arrayOf(1000, 2000, 3000)) + val data = wrappedArrayOfInteger.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxWrapper = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val getGenericData = classLoader.createTaskFor(taskFactory, GetGenericData::class.java) + val result = getGenericData.apply(sandboxWrapper) ?: fail("Result cannot be null") + + assertEquals("[Lsandbox.java.lang.Integer;", result::class.java.name) + assertThat(classLoader.createBasicOutput().apply(result)) + .isEqualTo(arrayOf(1000, 2000, 3000)) + } + } + + @Test + fun `test deserializing generic wrapper with primitive int array`() { + val wrappedArrayOfInteger = GenericWrapper(intArrayOf(1000, 2000, 3000)) + val data = wrappedArrayOfInteger.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxWrapper = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val getGenericData = classLoader.createTaskFor(taskFactory, GetGenericData::class.java) + val result = getGenericData.apply(sandboxWrapper) ?: fail("Result cannot be null") + + assertEquals("[I", result::class.java.name) + assertThat(classLoader.createBasicOutput().apply(result)) + .isEqualTo(intArrayOf(1000, 2000, 3000)) + } + } + + @Test + fun `test deserializing generic list`() { + val wrappedList = GenericWrapper(data = listOf("Hello World!")) + val data = wrappedList.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxWrapper = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val getGenericData = classLoader.createTaskFor(taskFactory, GetGenericData::class.java) + val dataResult = getGenericData.apply(sandboxWrapper) ?: fail("Result cannot be null") + + assertEquals("[Hello World!]", dataResult.toString()) + assertEquals("sandbox.java.util.Collections\$UnmodifiableRandomAccessList", dataResult::class.java.name) + + val getGenericIterableData = classLoader.createTaskFor(taskFactory, GetGenericIterableData::class.java) + val dataItemResult = getGenericIterableData.apply(sandboxWrapper) ?: fail("Result cannot be null") + assertEquals(SANDBOX_STRING, dataItemResult::class.java.name) + } + } + + class GetGenericData : Function, Any?> { + override fun apply(input: GenericWrapper): Any? { + return input.data + } + } + + class GetGenericIterableData : Function>, Any?> { + override fun apply(input: GenericWrapper>): Any? { + return input.data.iterator().let { + if (it.hasNext()) { + it.next() + } else { + null + } + } + } + } + + @Test + fun `test deserializing concrete wrapper`() { + val wrapped = ConcreteWrapper( + first = GenericWrapper("Hello World"), + second = GenericWrapper('!') + ) + val data = wrapped.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxWrapped = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showConcreteWrapper = classLoader.createTaskFor(taskFactory, ShowConcreteWrapper::class.java) + val result = showConcreteWrapper.apply(sandboxWrapped) ?: fail("Result cannot be null") + + assertEquals("Concrete: first='Hello World', second='!'", result.toString()) + } + } + + class ShowConcreteWrapper : Function { + override fun apply(input: ConcreteWrapper): String { + return "Concrete: first='${input.first.data}', second='${input.second.data}'" + } + } +} + +@CordaSerializable +data class GenericWrapper(val data: T) + +@CordaSerializable +data class ConcreteWrapper( + val first: GenericWrapper, + val second: GenericWrapper +) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeInputStreamTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeInputStreamTest.kt new file mode 100644 index 0000000000..5a4d096316 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeInputStreamTest.kt @@ -0,0 +1,43 @@ +package net.corda.serialization.djvm + +import net.corda.core.internal.readFully +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.io.InputStream +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeInputStreamTest : TestBase(KOTLIN) { + companion object { + const val MESSAGE = "Round and round the rugged rocks..." + } + + @Test + fun `test deserializing input stream`() { + val data = MESSAGE.byteInputStream().serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxStream = data.deserializeTypeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showInputStream = classLoader.createTaskFor(taskFactory, ShowInputStream::class.java) + val result = showInputStream.apply(sandboxStream) ?: fail("Result cannot be null") + + assertEquals(String(MESSAGE.byteInputStream().readFully()), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowInputStream : Function { + override fun apply(input: InputStream): String { + return String(input.readFully()) + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeInstantTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeInstantTest.kt new file mode 100644 index 0000000000..d0330bd999 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeInstantTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.Instant +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeInstantTest : TestBase(KOTLIN) { + @Test + fun `test deserializing instant`() { + val instant = Instant.now() + val data = instant.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxInstant = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showInstant = classLoader.createTaskFor(taskFactory, ShowInstant::class.java) + val result = showInstant.apply(sandboxInstant) ?: fail("Result cannot be null") + + assertEquals(instant.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowInstant : Function { + override fun apply(instant: Instant): String { + return instant.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeJavaWithMultipleConstructorsTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeJavaWithMultipleConstructorsTest.kt new file mode 100644 index 0000000000..c755f7381e --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeJavaWithMultipleConstructorsTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail + +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeJavaWithMultipleConstructorsTest : TestBase(KOTLIN) { + @Test + fun `test deserializing existing class`() { + val multiData = MultiConstructorData("Hello World", Long.MAX_VALUE, '!') + val data = multiData.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxData = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showMultiData = classLoader.createTaskFor(taskFactory, ShowMultiData::class.java) + val result = showMultiData.apply(sandboxData) ?: fail("Result cannot be null") + + assertThat(result.toString()) + .isEqualTo("MultiConstructor[message='Hello World', bigNumber=9223372036854775807, tag=!]") + } + } + + class ShowMultiData : Function { + override fun apply(data: MultiConstructorData): String { + return data.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeKotlinAliasTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeKotlinAliasTest.kt new file mode 100644 index 0000000000..420efb5ed1 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeKotlinAliasTest.kt @@ -0,0 +1,69 @@ +package net.corda.serialization.djvm + +import net.corda.core.crypto.SecureHash +import net.corda.core.node.services.AttachmentId +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeKotlinAliasTest : TestBase(KOTLIN) { + @Test + fun `test deserializing kotlin alias`() { + val attachmentId = SecureHash.allOnesHash + val data = attachmentId.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxAttachmentId = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showAlias = classLoader.createTaskFor(taskFactory, ShowAlias::class.java) + val result = showAlias.apply(sandboxAttachmentId) ?: fail("Result cannot be null") + + assertEquals(ShowAlias().apply(attachmentId), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowAlias : Function { + override fun apply(id: AttachmentId): String { + return id.toString() + } + } + + @Test + fun `test deserializing data with kotlin alias`() { + val attachment = AttachmentData(SecureHash.allOnesHash) + val data = attachment.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxAttachment = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showAliasData = classLoader.createTaskFor(taskFactory, ShowAliasData::class.java) + val result = showAliasData.apply(sandboxAttachment) ?: fail("Result cannot be null") + + assertEquals(ShowAliasData().apply(attachment), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowAliasData: Function { + override fun apply(data: AttachmentData): String { + return data.toString() + } + } +} + +@CordaSerializable +data class AttachmentData(val id: AttachmentId) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalDateTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalDateTest.kt new file mode 100644 index 0000000000..4f416bb403 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalDateTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.LocalDate +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeLocalDateTest : TestBase(KOTLIN) { + @Test + fun `test deserializing local date`() { + val date = LocalDate.now() + val data = date.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxDate = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showLocalDate = classLoader.createTaskFor(taskFactory, ShowLocalDate::class.java) + val result = showLocalDate.apply(sandboxDate) ?: fail("Result cannot be null") + + assertEquals(date.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowLocalDate : Function { + override fun apply(date: LocalDate): String { + return date.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalDateTimeTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalDateTimeTest.kt new file mode 100644 index 0000000000..b2077f1eb4 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalDateTimeTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.LocalDateTime +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeLocalDateTimeTest : TestBase(KOTLIN) { + @Test + fun `test deserializing local date-time`() { + val dateTime = LocalDateTime.now() + val data = dateTime.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxDateTime = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showLocalDateTime = classLoader.createTaskFor(taskFactory, ShowLocalDateTime::class.java) + val result = showLocalDateTime.apply( sandboxDateTime) ?: fail("Result cannot be null") + + assertEquals(dateTime.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowLocalDateTime : Function { + override fun apply(dateTime: LocalDateTime): String { + return dateTime.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalTimeTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalTimeTest.kt new file mode 100644 index 0000000000..d8ba1d28b0 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeLocalTimeTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.LocalTime +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeLocalTimeTest : TestBase(KOTLIN) { + @Test + fun `test deserializing local time`() { + val time = LocalTime.now() + val data = time.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxTime = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showLocalTime = classLoader.createTaskFor(taskFactory, ShowLocalTime::class.java) + val result = showLocalTime.apply(sandboxTime) ?: fail("Result cannot be null") + + assertEquals(time.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowLocalTime : Function { + override fun apply(time: LocalTime): String { + return time.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeMapsTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeMapsTest.kt new file mode 100644 index 0000000000..75885c9659 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeMapsTest.kt @@ -0,0 +1,202 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.EnumMap +import java.util.NavigableMap +import java.util.SortedMap +import java.util.TreeMap +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeMapsTest : TestBase(KOTLIN) { + @Test + fun `test deserializing map`() { + val stringMap = StringMap(mapOf("Open" to "Hello World", "Close" to "Goodbye, Cruel World")) + val data = stringMap.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMap = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringMap = classLoader.createTaskFor(taskFactory, ShowStringMap::class.java) + val result = showStringMap.apply(sandboxMap) ?: fail("Result cannot be null") + + assertEquals(stringMap.values.entries.joinToString(), result.toString()) + assertEquals("Open=Hello World, Close=Goodbye, Cruel World", result.toString()) + } + } + + class ShowStringMap : Function { + override fun apply(data: StringMap): String { + return data.values.entries.joinToString() + } + } + + @Test + fun `test deserializing sorted map`() { + val sortedMap = StringSortedMap(sortedMapOf( + 100 to "Goodbye, Cruel World", + 10 to "Hello World", + 50 to "Having Fun!" + )) + val data = sortedMap.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMap = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringSortedMap = classLoader.createTaskFor(taskFactory, ShowStringSortedMap::class.java) + val result = showStringSortedMap.apply(sandboxMap) ?: fail("Result cannot be null") + + assertEquals(sortedMap.values.entries.joinToString(), result.toString()) + assertEquals("10=Hello World, 50=Having Fun!, 100=Goodbye, Cruel World", result.toString()) + } + } + + class ShowStringSortedMap : Function { + override fun apply(data: StringSortedMap): String { + return data.values.entries.joinToString() + } + } + + @Test + fun `test deserializing navigable map`() { + val navigableMap = StringNavigableMap(mapOf( + 10000L to "Goodbye, Cruel World", + 1000L to "Hello World", + 5000L to "Having Fun!" + ).toMap(TreeMap())) + val data = navigableMap.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMap = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringNavigableMap = classLoader.createTaskFor(taskFactory, ShowStringNavigableMap::class.java) + val result = showStringNavigableMap.apply(sandboxMap) ?: fail("Result cannot be null") + + assertEquals(navigableMap.values.entries.joinToString(), result.toString()) + assertEquals("1000=Hello World, 5000=Having Fun!, 10000=Goodbye, Cruel World", result.toString()) + } + } + + class ShowStringNavigableMap : Function { + override fun apply(data: StringNavigableMap): String { + return data.values.entries.joinToString() + } + } + + @Test + fun `test deserializing linked hash map`() { + val linkedHashMap = StringLinkedHashMap(linkedMapOf( + "Close" to "Goodbye, Cruel World", + "Open" to "Hello World", + "During" to "Having Fun!" + )) + val data = linkedHashMap.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMap = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringLinkedHashMap = classLoader.createTaskFor(taskFactory, ShowStringLinkedHashMap::class.java) + val result = showStringLinkedHashMap.apply(sandboxMap) ?: fail("Result cannot be null") + + assertEquals(linkedHashMap.values.entries.joinToString(), result.toString()) + assertEquals("Close=Goodbye, Cruel World, Open=Hello World, During=Having Fun!", result.toString()) + } + } + + class ShowStringLinkedHashMap : Function { + override fun apply(data: StringLinkedHashMap): String { + return data.values.entries.joinToString() + } + } + + @Test + fun `test deserializing tree map`() { + val treeMap = StringTreeMap(mapOf( + 10000 to "Goodbye, Cruel World", + 1000 to "Hello World", + 5000 to "Having Fun!" + ).toMap(TreeMap())) + val data = treeMap.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMap = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringTreeMap = classLoader.createTaskFor(taskFactory, ShowStringTreeMap::class.java) + val result = showStringTreeMap.apply(sandboxMap) ?: fail("Result cannot be null") + + assertEquals(treeMap.values.entries.joinToString(), result.toString()) + assertEquals("1000=Hello World, 5000=Having Fun!, 10000=Goodbye, Cruel World", result.toString()) + } + } + + class ShowStringTreeMap : Function { + override fun apply(data: StringTreeMap): String { + return data.values.entries.joinToString() + } + } + + @Test + fun `test deserializing enum map`() { + val enumMap = EnumMap(mapOf( + ExampleEnum.ONE to "One!", + ExampleEnum.TWO to "Two!" + )) + val data = enumMap.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMap = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showEnumMap = classLoader.createTaskFor(taskFactory, ShowEnumMap::class.java) + val result = showEnumMap.apply(sandboxMap) ?: fail("Result cannot be null") + + assertEquals(enumMap.toString(), result.toString()) + assertEquals("{ONE=One!, TWO=Two!}", result.toString()) + } + } + + class ShowEnumMap : Function, String> { + override fun apply(data: EnumMap<*, String>): String { + return data.toString() + } + } +} + +@CordaSerializable +class StringMap(val values: Map) + +@CordaSerializable +class StringSortedMap(val values: SortedMap) + +@CordaSerializable +class StringNavigableMap(val values: NavigableMap) + +@CordaSerializable +class StringLinkedHashMap(val values: LinkedHashMap) + +@CordaSerializable +class StringTreeMap(val values: TreeMap) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeMonthDayTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeMonthDayTest.kt new file mode 100644 index 0000000000..425a289cb5 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeMonthDayTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.MonthDay +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeMonthDayTest : TestBase(KOTLIN) { + @Test + fun `test deserializing month-day`() { + val monthDay = MonthDay.now() + val data = monthDay.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxMonthDay = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showMonthDay = classLoader.createTaskFor(taskFactory, ShowMonthDay::class.java) + val result = showMonthDay.apply(sandboxMonthDay) ?: fail("Result cannot be null") + + assertEquals(monthDay.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowMonthDay : Function { + override fun apply(monthDay: MonthDay): String { + return monthDay.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeObjectArraysTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeObjectArraysTest.kt new file mode 100644 index 0000000000..835e62a8c4 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeObjectArraysTest.kt @@ -0,0 +1,298 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.UUID +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeObjectArraysTest : TestBase(KOTLIN) { + @Test + fun `test deserializing string array`() { + val stringArray = HasStringArray(arrayOf("Hello", "World", "!")) + val data = stringArray.serialize() + assertEquals("Hello, World, !", ShowStringArray().apply(stringArray)) + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringArray = classLoader.createTaskFor(taskFactory, ShowStringArray::class.java) + val result = showStringArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals(SANDBOX_STRING, result::class.java.name) + assertEquals("Hello, World, !", result.toString()) + } + } + + class ShowStringArray : Function { + override fun apply(data: HasStringArray): String { + return data.lines.joinToString() + } + } + + @Test + fun `test deserializing character array`() { + val charArray = HasCharacterArray(arrayOf('H', 'e', 'l', 'l', 'o', '!')) + val data = charArray.serialize() + assertEquals("Hello!", ShowCharacterArray().apply(charArray)) + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showCharacterArray = classLoader.createTaskFor(taskFactory, ShowCharacterArray::class.java) + val result = showCharacterArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals(SANDBOX_STRING, result::class.java.name) + assertEquals("Hello!", result.toString()) + } + } + + class ShowCharacterArray : Function { + override fun apply(data: HasCharacterArray): String { + return data.letters.joinTo(StringBuilder(), separator = "").toString() + } + } + + @Test + fun `test deserializing long array`() { + val longArray = HasLongArray(arrayOf(1000, 2000, 3000, 4000, 5000)) + val data = longArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showLongArray = classLoader.createTaskFor(taskFactory, ShowLongArray::class.java) + val result = showLongArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Long", result::class.java.name) + assertEquals("15000", result.toString()) + } + } + + class ShowLongArray : Function { + override fun apply(data: HasLongArray): Long { + return data.longs.sum() + } + } + + @Test + fun `test deserializing integer array`() { + val integerArray = HasIntegerArray(arrayOf(100, 200, 300, 400, 500)) + val data = integerArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showIntegerArray = classLoader.createTaskFor(taskFactory, ShowIntegerArray::class.java) + val result = showIntegerArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals("1500", result.toString()) + } + } + + class ShowIntegerArray : Function { + override fun apply(data: HasIntegerArray): Int { + return data.integers.sum() + } + } + + @Test + fun `test deserializing short array`() { + val shortArray = HasShortArray(arrayOf(100, 200, 300, 400, 500)) + val data = shortArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showShortArray = classLoader.createTaskFor(taskFactory, ShowShortArray::class.java) + val result = showShortArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals("1500", result.toString()) + } + } + + class ShowShortArray : Function { + override fun apply(data: HasShortArray): Int { + return data.shorts.sum() + } + } + + @Test + fun `test deserializing byte array`() { + val byteArray = HasByteArray(arrayOf(10, 20, 30, 40, 50)) + val data = byteArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showByteArray = classLoader.createTaskFor(taskFactory, ShowByteArray::class.java) + val result = showByteArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals("150", result.toString()) + } + } + + class ShowByteArray : Function { + override fun apply(data: HasByteArray): Int { + return data.bytes.sum() + } + } + + @Test + fun `test deserializing double array`() { + val doubleArray = HasDoubleArray(arrayOf(1000.0, 2000.0, 3000.0, 4000.0, 5000.0)) + val data = doubleArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showDoubleArray = classLoader.createTaskFor(taskFactory, ShowDoubleArray::class.java) + val result = showDoubleArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Double", result::class.java.name) + assertEquals("15000.0", result.toString()) + } + } + + class ShowDoubleArray : Function { + override fun apply(data: HasDoubleArray): Double { + return data.doubles.sum() + } + } + + @Test + fun `test deserializing float array`() { + val floatArray = HasFloatArray(arrayOf(10.0f, 20.0f, 30.0f, 40.0f, 50.0f)) + val data = floatArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showFloatArray = classLoader.createTaskFor(taskFactory, ShowFloatArray::class.java) + val result = showFloatArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Float", result::class.java.name) + assertEquals("150.0", result.toString()) + } + } + + class ShowFloatArray : Function { + override fun apply(data: HasFloatArray): Float { + return data.floats.sum() + } + } + + @Test + fun `test deserializing boolean array`() { + val booleanArray = HasBooleanArray(arrayOf(true, true, true)) + val data = booleanArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showBooleanArray = classLoader.createTaskFor(taskFactory, ShowBooleanArray::class.java) + val result = showBooleanArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Boolean", result::class.java.name) + assertEquals("true", result.toString()) + } + } + + class ShowBooleanArray : Function { + override fun apply(data: HasBooleanArray): Boolean { + return data.bools.all { it } + } + } + + @Test + fun `test deserializing uuid array`() { + val uuid = UUID.randomUUID() + val uuidArray = HasUUIDArray(arrayOf(uuid)) + val data = uuidArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showUUIDArray = classLoader.createTaskFor(taskFactory, ShowUUIDArray::class.java) + val result = showUUIDArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals(SANDBOX_STRING, result::class.java.name) + assertEquals(uuid.toString(), result.toString()) + } + } + + class ShowUUIDArray : Function { + override fun apply(data: HasUUIDArray): String { + return data.uuids.joinTo(StringBuilder()).toString() + } + } +} + +@CordaSerializable +class HasStringArray(val lines: Array) + +@CordaSerializable +class HasCharacterArray(val letters: Array) + +@CordaSerializable +class HasLongArray(val longs: Array) + +@CordaSerializable +class HasIntegerArray(val integers: Array) + +@CordaSerializable +class HasShortArray(val shorts: Array) + +@CordaSerializable +class HasByteArray(val bytes: Array) + +@CordaSerializable +class HasDoubleArray(val doubles: Array) + +@CordaSerializable +class HasFloatArray(val floats: Array) + +@CordaSerializable +class HasBooleanArray(val bools: Array) + +@CordaSerializable +class HasUUIDArray(val uuids: Array) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOffsetDateTimeTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOffsetDateTimeTest.kt new file mode 100644 index 0000000000..c8b6aa6af9 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOffsetDateTimeTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.OffsetDateTime +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeOffsetDateTimeTest : TestBase(KOTLIN) { + @Test + fun `test deserializing offset date-time`() { + val dateTime = OffsetDateTime.now() + val data = dateTime.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxDateTime = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showOffsetDateTime = classLoader.createTaskFor(taskFactory, ShowOffsetDateTime::class.java) + val result = showOffsetDateTime.apply(sandboxDateTime) ?: fail("Result cannot be null") + + assertEquals(dateTime.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowOffsetDateTime : Function { + override fun apply(dateTime: OffsetDateTime): String { + return dateTime.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOffsetTimeTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOffsetTimeTest.kt new file mode 100644 index 0000000000..2829f11259 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOffsetTimeTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.OffsetTime +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeOffsetTimeTest : TestBase(KOTLIN) { + @Test + fun `test deserializing instant`() { + val time = OffsetTime.now() + val data = time.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxTime = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showOffsetTime = classLoader.createTaskFor(taskFactory, ShowOffsetTime::class.java) + val result = showOffsetTime.apply(sandboxTime) ?: fail("Result cannot be null") + + assertEquals(time.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowOffsetTime : Function { + override fun apply(time: OffsetTime): String { + return time.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOpaqueBytesSubSequenceTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOpaqueBytesSubSequenceTest.kt new file mode 100644 index 0000000000..0ff0a0deca --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOpaqueBytesSubSequenceTest.kt @@ -0,0 +1,48 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytesSubSequence +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeOpaqueBytesSubSequenceTest : TestBase(KOTLIN) { + companion object { + const val MESSAGE = "The rain in spain falls mainly on the plain." + const val OFFSET = MESSAGE.length / 2 + } + + @Test + fun `test deserializing opaquebytes subsequence`() { + val subSequence = OpaqueBytesSubSequence( + bytes = MESSAGE.toByteArray(), + offset = OFFSET, + size = MESSAGE.length - OFFSET + ) + val data = subSequence.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxBytes = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showOpaqueBytesSubSequence = classLoader.createTaskFor(taskFactory, ShowOpaqueBytesSubSequence::class.java) + val result = showOpaqueBytesSubSequence.apply(sandboxBytes) ?: fail("Result cannot be null") + + assertEquals(MESSAGE.substring(OFFSET), String(result as ByteArray)) + assertEquals(String(subSequence.copyBytes()), String(result)) + } + } + + class ShowOpaqueBytesSubSequence : Function { + override fun apply(sequence: OpaqueBytesSubSequence): ByteArray { + return sequence.copyBytes() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOptionalTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOptionalTest.kt new file mode 100644 index 0000000000..5914d75dac --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeOptionalTest.kt @@ -0,0 +1,58 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.Optional +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeOptionalTest : TestBase(KOTLIN) { + @Test + fun `test deserializing optional with object`() { + val optional = Optional.of("Hello World!") + val data = optional.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxOptional = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showOptional = classLoader.createTaskFor(taskFactory, ShowOptional::class.java) + val result = showOptional.apply(sandboxOptional) ?: fail("Result cannot be null") + + assertEquals("Optional -> Optional[Hello World!]", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + @Test + fun `test deserializing optional without object`() { + val optional = Optional.empty() + val data = optional.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxOptional = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showOptional = classLoader.createTaskFor(taskFactory, ShowOptional::class.java) + val result = showOptional.apply(sandboxOptional) ?: fail("Result cannot be null") + + assertEquals("Optional -> Optional.empty", result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } +} + +class ShowOptional : Function, String> { + override fun apply(optional: Optional<*>): String { + return "Optional -> $optional" + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePeriodTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePeriodTest.kt new file mode 100644 index 0000000000..372a9b473a --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePeriodTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.Period +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializePeriodTest : TestBase(KOTLIN) { + @Test + fun `test deserializing period`() { + val period = Period.of(1, 2, 3) + val data = period.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxPeriod = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showPeriod = classLoader.createTaskFor(taskFactory, ShowPeriod::class.java) + val result = showPeriod.apply(sandboxPeriod) ?: fail("Result cannot be null") + + assertEquals(period.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowPeriod : Function { + override fun apply(period: Period): String { + return period.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePrimitiveArraysTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePrimitiveArraysTest.kt new file mode 100644 index 0000000000..7a7530982d --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePrimitiveArraysTest.kt @@ -0,0 +1,239 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializePrimitiveArraysTest : TestBase(KOTLIN) { + @Test + fun `test deserializing character array`() { + val charArray = PrimitiveCharArray(charArrayOf('H', 'e', 'l', 'l', 'o', '!')) + val data = charArray.serialize() + assertEquals("Hello!", ShowCharArray().apply(charArray)) + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showCharArray = classLoader.createTaskFor(taskFactory, ShowCharArray::class.java) + val result = showCharArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals(SANDBOX_STRING, result::class.java.name) + assertEquals("Hello!", result.toString()) + } + } + + class ShowCharArray : Function { + override fun apply(data: PrimitiveCharArray): String { + return data.letters.joinTo(StringBuilder(), separator = "").toString() + } + } + + @Test + fun `test deserializing integer array`() { + val intArray = PrimitiveIntegerArray(intArrayOf(100, 200, 300, 400, 500)) + val data = intArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showIntegerArray = classLoader.createTaskFor(taskFactory, ShowIntegerArray::class.java) + val result = showIntegerArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals("1500", result.toString()) + } + } + + class ShowIntegerArray : Function { + override fun apply(data: PrimitiveIntegerArray): Int { + return data.integers.sum() + } + } + + @Test + fun `test deserializing long array`() { + val longArray = PrimitiveLongArray(longArrayOf(1000, 2000, 3000, 4000, 5000)) + val data = longArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showLongArray = classLoader.createTaskFor(taskFactory, ShowLongArray::class.java) + val result = showLongArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Long", result::class.java.name) + assertEquals("15000", result.toString()) + } + } + + class ShowLongArray : Function { + override fun apply(data: PrimitiveLongArray): Long { + return data.longs.sum() + } + } + + @Test + fun `test deserializing short array`() { + val shortArray = PrimitiveShortArray(shortArrayOf(100, 200, 300, 400, 500)) + val data = shortArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showShortArray = classLoader.createTaskFor(taskFactory, ShowShortArray::class.java) + val result = showShortArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals("1500", result.toString()) + } + } + + class ShowShortArray : Function { + override fun apply(data: PrimitiveShortArray): Int { + return data.shorts.sum() + } + } + + @Test + fun `test deserializing byte array`() { + val byteArray = PrimitiveByteArray(byteArrayOf(10, 20, 30, 40, 50)) + val data = byteArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showByteArray = classLoader.createTaskFor(taskFactory, ShowByteArray::class.java) + val result = showByteArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Integer", result::class.java.name) + assertEquals("150", result.toString()) + } + } + + class ShowByteArray : Function { + override fun apply(data: PrimitiveByteArray): Int { + return data.bytes.sum() + } + } + + @Test + fun `test deserializing boolean array`() { + val booleanArray = PrimitiveBooleanArray(booleanArrayOf(true, true)) + val data = booleanArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showBooleanArray = classLoader.createTaskFor(taskFactory, ShowBooleanArray::class.java) + val result = showBooleanArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Boolean", result::class.java.name) + assertEquals("true", result.toString()) + } + } + + class ShowBooleanArray : Function { + override fun apply(data: PrimitiveBooleanArray): Boolean { + return data.bools.all { it } + } + } + + @Test + fun `test deserializing double array`() { + val doubleArray = PrimitiveDoubleArray(doubleArrayOf(1000.0, 2000.0, 3000.0, 4000.0, 5000.0)) + val data = doubleArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showDoubleArray = classLoader.createTaskFor(taskFactory, ShowDoubleArray::class.java) + val result = showDoubleArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Double", result::class.java.name) + assertEquals("15000.0", result.toString()) + } + } + + class ShowDoubleArray : Function { + override fun apply(data: PrimitiveDoubleArray): Double { + return data.doubles.sum() + } + } + + @Test + fun `test deserializing float array`() { + val floatArray = PrimitiveFloatArray(floatArrayOf(100.0f, 200.0f, 300.0f, 400.0f, 500.0f)) + val data = floatArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showFloatArray = classLoader.createTaskFor(taskFactory, ShowFloatArray::class.java) + val result = showFloatArray.apply(sandboxArray) ?: fail("Result cannot be null") + + assertEquals("sandbox.java.lang.Float", result::class.java.name) + assertEquals("1500.0", result.toString()) + } + } + + class ShowFloatArray : Function { + override fun apply(data: PrimitiveFloatArray): Float { + return data.floats.sum() + } + } +} + +@CordaSerializable +class PrimitiveCharArray(val letters: CharArray) + +@CordaSerializable +class PrimitiveShortArray(val shorts: ShortArray) + +@CordaSerializable +class PrimitiveIntegerArray(val integers: IntArray) + +@CordaSerializable +class PrimitiveLongArray(val longs: LongArray) + +@CordaSerializable +class PrimitiveByteArray(val bytes: ByteArray) + +@CordaSerializable +class PrimitiveBooleanArray(val bools: BooleanArray) + +@CordaSerializable +class PrimitiveDoubleArray(val doubles: DoubleArray) + +@CordaSerializable +class PrimitiveFloatArray(val floats: FloatArray) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePrimitivesTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePrimitivesTest.kt new file mode 100644 index 0000000000..afc3b3dedc --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePrimitivesTest.kt @@ -0,0 +1,76 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import java.util.Date +import java.util.UUID + +@ExtendWith(LocalSerialization::class) +class DeserializePrimitivesTest : TestBase(KOTLIN) { + @Test + fun `test naked uuid`() { + val uuid = UUID.randomUUID() + val data = uuid.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxUUID = data.deserializeFor(classLoader) + assertEquals(uuid.toString(), sandboxUUID.toString()) + assertEquals("sandbox.${uuid::class.java.name}", sandboxUUID::class.java.name) + } + } + + @Test + fun `test wrapped uuid`() { + val uuid = WrappedUUID(UUID.randomUUID()) + val data = uuid.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxUUID = data.deserializeFor(classLoader) + assertEquals(uuid.toString(), sandboxUUID.toString()) + assertEquals("sandbox.${uuid::class.java.name}", sandboxUUID::class.java.name) + } + } + + @Test + fun `test naked date`() { + val now = Date() + val data = now.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxNow = data.deserializeFor(classLoader) + assertEquals(now.toString(), sandboxNow.toString()) + assertEquals("sandbox.${now::class.java.name}", sandboxNow::class.java.name) + } + } + + @Test + fun `test wrapped date`() { + val now = WrappedDate(Date()) + val data = now.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxNow = data.deserializeFor(classLoader) + assertEquals(now.toString(), sandboxNow.toString()) + assertEquals("sandbox.${now::class.java.name}", sandboxNow::class.java.name) + } + } +} + +@CordaSerializable +data class WrappedUUID(val uuid: UUID) + +@CordaSerializable +data class WrappedDate(val date: Date) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeProtonJTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeProtonJTest.kt new file mode 100644 index 0000000000..a73b7a8ddc --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeProtonJTest.kt @@ -0,0 +1,245 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.apache.qpid.proton.amqp.Decimal128 +import org.apache.qpid.proton.amqp.Decimal32 +import org.apache.qpid.proton.amqp.Decimal64 +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.amqp.UnsignedByte +import org.apache.qpid.proton.amqp.UnsignedInteger +import org.apache.qpid.proton.amqp.UnsignedLong +import org.apache.qpid.proton.amqp.UnsignedShort +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeProtonJTest : TestBase(KOTLIN) { + @Test + fun `test deserializing unsigned long`() { + val protonJ = HasUnsignedLong(UnsignedLong.valueOf(12345678)) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showUnsignedLong = classLoader.createTaskFor(taskFactory, ShowUnsignedLong::class.java) + val result = showUnsignedLong.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.number.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowUnsignedLong : Function { + override fun apply(data: HasUnsignedLong): String { + return data.number.toString() + } + } + + @Test + fun `test deserializing unsigned integer`() { + val protonJ = HasUnsignedInteger(UnsignedInteger.valueOf(123456)) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showUnsignedInteger = classLoader.createTaskFor(taskFactory, ShowUnsignedInteger::class.java) + val result = showUnsignedInteger.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.number.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowUnsignedInteger : Function { + override fun apply(data: HasUnsignedInteger): String { + return data.number.toString() + } + } + + @Test + fun `test deserializing unsigned short`() { + val protonJ = HasUnsignedShort(UnsignedShort.valueOf(12345)) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showUnsignedShort = classLoader.createTaskFor(taskFactory, ShowUnsignedShort::class.java) + val result = showUnsignedShort.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.number.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowUnsignedShort : Function { + override fun apply(data: HasUnsignedShort): String { + return data.number.toString() + } + } + + @Test + fun `test deserializing unsigned byte`() { + val protonJ = HasUnsignedByte(UnsignedByte.valueOf(0x8f.toByte())) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showUnsignedByte = classLoader.createTaskFor(taskFactory, ShowUnsignedByte::class.java) + val result = showUnsignedByte.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.number.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowUnsignedByte : Function { + override fun apply(data: HasUnsignedByte): String { + return data.number.toString() + } + } + + @Test + fun `test deserializing 128 bit decimal`() { + val protonJ = HasDecimal128(Decimal128(12345678, 98765432)) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showDecimal128 = classLoader.createTaskFor(taskFactory, ShowDecimal128::class.java) + val result = showDecimal128.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertThat(result) + .isEqualTo(protonJ.number.let { longArrayOf(it.mostSignificantBits, it.leastSignificantBits) }) + } + } + + class ShowDecimal128 : Function { + override fun apply(data: HasDecimal128): LongArray { + return data.number.let { longArrayOf(it.mostSignificantBits, it.leastSignificantBits) } + } + } + + @Test + fun `test deserializing 64 bit decimal`() { + val protonJ = HasDecimal64(Decimal64(98765432)) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showDecimal64 = classLoader.createTaskFor(taskFactory, ShowDecimal64::class.java) + val result = showDecimal64.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.number.bits.toString(), result.toString()) + } + } + + class ShowDecimal64 : Function { + override fun apply(data: HasDecimal64): Long { + return data.number.bits + } + } + + @Test + fun `test deserializing 32 bit decimal`() { + val protonJ = HasDecimal32(Decimal32(123456)) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showDecimal32 = classLoader.createTaskFor(taskFactory, ShowDecimal32::class.java) + val result = showDecimal32.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.number.bits.toString(), result.toString()) + } + } + + class ShowDecimal32 : Function { + override fun apply(data: HasDecimal32): Int { + return data.number.bits + } + } + + @Test + fun `test deserializing symbol`() { + val protonJ = HasSymbol(Symbol.valueOf("-my-symbol-value-")) + val data = protonJ.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxProtonJ = data.deserializeFor(classLoader) + + val executor = classLoader.createRawTaskFactory() + val taskFactory = classLoader.createTaskFor(executor, ShowSymbol::class.java) + val result = taskFactory.apply(sandboxProtonJ) ?: fail("Result cannot be null") + + assertEquals(protonJ.symbol.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowSymbol : Function { + override fun apply(data: HasSymbol): String { + return data.symbol.toString() + } + } +} + +@CordaSerializable +class HasUnsignedLong(val number: UnsignedLong) + +@CordaSerializable +class HasUnsignedInteger(val number: UnsignedInteger) + +@CordaSerializable +class HasUnsignedShort(val number: UnsignedShort) + +@CordaSerializable +class HasUnsignedByte(val number: UnsignedByte) + +@CordaSerializable +class HasDecimal32(val number: Decimal32) + +@CordaSerializable +class HasDecimal64(val number: Decimal64) + +@CordaSerializable +class HasDecimal128(val number: Decimal128) + +@CordaSerializable +class HasSymbol(val symbol: Symbol) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt new file mode 100644 index 0000000000..26bc1c700f --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt @@ -0,0 +1,92 @@ +package net.corda.serialization.djvm + +import net.corda.core.crypto.CompositeKey +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SignatureScheme +import net.corda.core.internal.hash +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import java.security.PublicKey +import java.util.function.Function +import java.util.stream.Stream + +@ExtendWith(LocalSerialization::class) +class DeserializePublicKeyTest : TestBase(KOTLIN) { + class SignatureSchemeProvider : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream { + return Crypto.supportedSignatureSchemes().stream() + .filter { it != Crypto.COMPOSITE_KEY } + .map { Arguments.of(it) } + } + } + + @ArgumentsSource(SignatureSchemeProvider::class) + @ParameterizedTest(name = "{index} => {0}") + fun `test deserializing public key`(signatureScheme: SignatureScheme) { + val keyPair = Crypto.generateKeyPair(signatureScheme) + val publicKey = PublicKeyData(keyPair.public) + val data = publicKey.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxKey = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showPublicKey = classLoader.createTaskFor(taskFactory, ShowPublicKey::class.java) + val result = showPublicKey.apply(sandboxKey) ?: fail("Result cannot be null") + + assertEquals(ShowPublicKey().apply(publicKey), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + @Test + fun `test composite public key`() { + val key1 = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256).public + val key2 = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256).public + val key3 = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512).public + + val compositeKey = CompositeKey.Builder() + .addKey(key1, weight = 1) + .addKey(key2, weight = 1) + .addKey(key3, weight = 1) + .build(2) + val compositeData = PublicKeyData(compositeKey) + val data = compositeData.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxKey = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showPublicKey = classLoader.createTaskFor(taskFactory, ShowPublicKey::class.java) + val result = showPublicKey.apply(sandboxKey) ?: fail("Result cannot be null") + + assertEquals(ShowPublicKey().apply(compositeData), result.toString()) + } + } + + class ShowPublicKey : Function { + override fun apply(data: PublicKeyData): String { + return with(data) { + "PublicKey: algorithm='${key.algorithm}', format='${key.format}', hash=${key.hash}" + } + } + } +} + +@CordaSerializable +data class PublicKeyData(val key: PublicKey) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeStringBufferTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeStringBufferTest.kt new file mode 100644 index 0000000000..5783c96c27 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeStringBufferTest.kt @@ -0,0 +1,38 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeStringBufferTest : TestBase(KOTLIN) { + @Test + fun `test deserializing string buffer`() { + val buffer = StringBuffer("Hello World!") + val data = buffer.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxBuffer = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringBuffer = classLoader.createTaskFor(taskFactory, ShowStringBuffer::class.java) + val result = showStringBuffer.apply(sandboxBuffer) ?: fail("Result cannot be null") + + assertEquals(ShowStringBuffer().apply(buffer), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowStringBuffer : Function { + override fun apply(buffer: StringBuffer): String { + return buffer.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeStringTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeStringTest.kt new file mode 100644 index 0000000000..7d39212e2d --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeStringTest.kt @@ -0,0 +1,71 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeStringTest : TestBase(KOTLIN) { + @Test + fun `test deserializing string`() { + val stringMessage = StringMessage("Hello World!") + val data = stringMessage.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxString = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringMessage = classLoader.createTaskFor(taskFactory, ShowStringMessage::class.java) + val result = showStringMessage.apply(sandboxString) ?: fail("Result cannot be null") + + assertEquals(stringMessage.message, result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowStringMessage : Function { + override fun apply(data: StringMessage): String { + return data.message + } + } + + @Test + fun `test deserializing string list of arrays`() { + val stringListArray = StringListOfArray(listOf( + arrayOf("Hello"), arrayOf("World"), arrayOf("!")) + ) + val data = stringListArray.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxListArray = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showStringListOfArray = classLoader.createTaskFor(taskFactory, ShowStringListOfArray::class.java) + val result = showStringListOfArray.apply(sandboxListArray) ?: fail("Result cannot be null") + + assertEquals(stringListArray.data.flatMap(Array::toList).joinToString(), result.toString()) + } + } + + class ShowStringListOfArray : Function { + override fun apply(obj: StringListOfArray): String { + return obj.data.flatMap(Array::toList).joinToString() + } + } +} + +@CordaSerializable +data class StringMessage(val message: String) + +@CordaSerializable +class StringListOfArray(val data: List>) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeYearMonthTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeYearMonthTest.kt new file mode 100644 index 0000000000..a8f87315c2 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeYearMonthTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.YearMonth +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeYearMonthTest : TestBase(KOTLIN) { + @Test + fun `test deserializing year-month`() { + val yearMonth = YearMonth.now() + val data = yearMonth.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxYearMonth = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showYearMonth = classLoader.createTaskFor(taskFactory, ShowYearMonth::class.java) + val result = showYearMonth.apply(sandboxYearMonth) ?: fail("Result cannot be null") + + assertEquals(yearMonth.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowYearMonth : Function { + override fun apply(yearMonth: YearMonth): String { + return yearMonth.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeYearTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeYearTest.kt new file mode 100644 index 0000000000..ce282a3e33 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeYearTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.Year +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeYearTest : TestBase(KOTLIN) { + @Test + fun `test deserializing year`() { + val year = Year.now() + val data = year.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxYear = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showYear = classLoader.createTaskFor(taskFactory, ShowYear::class.java) + val result = showYear.apply(sandboxYear) ?: fail("Result cannot be null") + + assertEquals(year.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowYear : Function { + override fun apply(year: Year): String { + return year.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeZoneIdTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeZoneIdTest.kt new file mode 100644 index 0000000000..5f29e943e7 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeZoneIdTest.kt @@ -0,0 +1,51 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import java.time.ZoneId +import java.util.function.Function +import java.util.stream.Stream + +@ExtendWith(LocalSerialization::class) +class DeserializeZoneIdTest : TestBase(KOTLIN) { + class ZoneIdProvider : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream { + return ZoneId.getAvailableZoneIds().stream() + .sorted().limit(10).map { Arguments.of(ZoneId.of(it)) } + } + } + + @ArgumentsSource(ZoneIdProvider::class) + @ParameterizedTest(name = "{index} => {0}") + fun `test deserializing zone id`(zoneId: ZoneId) { + val data = zoneId.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxZoneId = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showZoneId = classLoader.createTaskFor(taskFactory, ShowZoneId::class.java) + val result = showZoneId.apply(sandboxZoneId) ?: fail("Result cannot be null") + + assertEquals(zoneId.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowZoneId : Function { + override fun apply(zoneId: ZoneId): String { + return zoneId.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeZonedDateTimeTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeZonedDateTimeTest.kt new file mode 100644 index 0000000000..8bb463d788 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializeZonedDateTimeTest.kt @@ -0,0 +1,39 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.time.ZonedDateTime +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeZonedDateTimeTest : TestBase(KOTLIN) { + @Test + fun `test deserializing zoned date-time`() { + val dateTime = ZonedDateTime.now() + val data = dateTime.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxDateTime = data.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showZonedDateTime = classLoader.createTaskFor(taskFactory, ShowZonedDateTime::class.java) + val result = showZonedDateTime.apply(sandboxDateTime) ?: fail("Result cannot be null") + + assertEquals(dateTime.toString(), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowZonedDateTime : Function { + override fun apply(dateTime: ZonedDateTime): String { + return dateTime.toString() + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalSerialization.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalSerialization.kt new file mode 100644 index 0000000000..48046db4d0 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/LocalSerialization.kt @@ -0,0 +1,68 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationContext.UseCase +import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.core.serialization.SerializationWhitelist +import net.corda.core.serialization.internal.SerializationEnvironment +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.serialization.internal.BuiltInExceptionsWhitelist +import net.corda.serialization.internal.CordaSerializationMagic +import net.corda.serialization.internal.GlobalTransientClassWhiteList +import net.corda.serialization.internal.SerializationContextImpl +import net.corda.serialization.internal.SerializationFactoryImpl +import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap +import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey +import net.corda.serialization.internal.amqp.SerializerFactory +import net.corda.serialization.internal.amqp.amqpMagic +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext + +class LocalSerialization : BeforeEachCallback, AfterEachCallback { + private companion object { + private val AMQP_P2P_CONTEXT = SerializationContextImpl( + amqpMagic, + LocalSerialization::class.java.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + UseCase.P2P, + null + ) + } + + override fun beforeEach(context: ExtensionContext) { + _contextSerializationEnv.set(createTestSerializationEnv()) + } + + override fun afterEach(context: ExtensionContext) { + _contextSerializationEnv.set(null) + } + + private fun createTestSerializationEnv(): SerializationEnvironment { + val factory = SerializationFactoryImpl(mutableMapOf()).apply { + registerScheme(AMQPSerializationScheme(emptySet(), emptySet(), AccessOrderLinkedHashMap(128))) + } + return SerializationEnvironment.with(factory, AMQP_P2P_CONTEXT) + } + + private class AMQPSerializationScheme( + customSerializers: Set>, + serializationWhitelists: Set, + serializerFactoriesForContexts: AccessOrderLinkedHashMap + ) : AbstractAMQPSerializationScheme(customSerializers, serializationWhitelists, serializerFactoriesForContexts) { + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { + throw UnsupportedOperationException() + } + + override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { + throw UnsupportedOperationException() + } + + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: UseCase): Boolean { + return canDeserializeVersion(magic) && target == UseCase.P2P + } + } +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/SafeDeserialisationTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/SafeDeserialisationTest.kt new file mode 100644 index 0000000000..38758201bc --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/SafeDeserialisationTest.kt @@ -0,0 +1,94 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.serialization.djvm.SandboxType.KOTLIN +import net.corda.serialization.internal.SectionId +import net.corda.serialization.internal.amqp.CompositeType +import net.corda.serialization.internal.amqp.DeserializationInput +import net.corda.serialization.internal.amqp.Envelope +import net.corda.serialization.internal.amqp.TypeNotation +import net.corda.serialization.internal.amqp.alsoAsByteBuffer +import net.corda.serialization.internal.amqp.amqpMagic +import net.corda.serialization.internal.amqp.withDescribed +import net.corda.serialization.internal.amqp.withList +import org.apache.qpid.proton.codec.Data +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.io.ByteArrayOutputStream +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class SafeDeserialisationTest : TestBase(KOTLIN) { + companion object { + const val MESSAGE = "Nothing to see here..." + const val NUMBER = 123.toShort() + } + + @Test + fun `test deserialising an evil class`() { + val context = (_contextSerializationEnv.get() ?: fail("No serialization environment!")).p2pContext + + val innocent = InnocentData(MESSAGE, NUMBER) + val innocentData = innocent.serialize() + val envelope = DeserializationInput.getEnvelope(innocentData, context.encodingWhitelist).apply { + val innocentType = schema.types[0] as CompositeType + (schema.types as MutableList)[0] = CompositeType( + name = innocentType.name.replace("Innocent", "VeryEvil"), + label = innocentType.label, + provides = innocentType.provides, + descriptor = innocentType.descriptor, + fields = innocentType.fields + ) + } + val evilData = SerializedBytes(envelope.write()) + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxData = evilData.deserializeFor(classLoader) + + val taskFactory = classLoader.createRawTaskFactory() + val showInnocentData = classLoader.createTaskFor(taskFactory, ShowInnocentData::class.java) + val result = showInnocentData.apply(sandboxData) ?: fail("Result cannot be null") + + // Check that we have deserialised the data without instantiating the Evil class. + assertThat(result.toString()) + .isEqualTo("sandbox.net.corda.serialization.djvm.VeryEvilData: $MESSAGE, $NUMBER") + + // Check that instantiating the Evil class does indeed cause an error. + val ex = assertThrows{ VeryEvilData("Naughty!", 0) } + assertThat(ex.cause) + .isExactlyInstanceOf(IllegalStateException::class.java) + .hasMessageContaining("Victory is mine!") + } + } + + private fun Envelope.write(): ByteArray { + val data = Data.Factory.create() + data.withDescribed(Envelope.DESCRIPTOR_OBJECT) { + withList { + putObject(obj) + putObject(schema) + putObject(transformsSchema) + } + } + return ByteArrayOutputStream().use { + amqpMagic.writeTo(it) + SectionId.DATA_AND_STOP.writeTo(it) + it.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode) + it.toByteArray() + } + } + + class ShowInnocentData : Function { + override fun apply(data: InnocentData): String { + return "${data::class.java.name}: ${data.message}, ${data.number}" + } + } +} + diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/SandboxType.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/SandboxType.kt new file mode 100644 index 0000000000..6b2d5d827f --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/SandboxType.kt @@ -0,0 +1,6 @@ +package net.corda.serialization.djvm + +enum class SandboxType { + JAVA, + KOTLIN +} diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/TestBase.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/TestBase.kt new file mode 100644 index 0000000000..1e66cf06a8 --- /dev/null +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/TestBase.kt @@ -0,0 +1,118 @@ +package net.corda.serialization.djvm + +import net.corda.core.serialization.ConstructorForDeserialization +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization +import net.corda.djvm.SandboxConfiguration +import net.corda.djvm.SandboxRuntimeContext +import net.corda.djvm.analysis.AnalysisConfiguration +import net.corda.djvm.analysis.Whitelist.Companion.MINIMAL +import net.corda.djvm.execution.ExecutionProfile.Companion.UNLIMITED +import net.corda.djvm.messages.Severity +import net.corda.djvm.messages.Severity.WARNING +import net.corda.djvm.source.BootstrapClassLoader +import net.corda.djvm.source.UserPathSource +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.fail +import java.io.File +import java.nio.file.Files.exists +import java.nio.file.Files.isDirectory +import java.nio.file.Path +import java.nio.file.Paths +import java.util.function.Consumer +import kotlin.concurrent.thread + +@Suppress("unused", "MemberVisibilityCanBePrivate") +abstract class TestBase(type: SandboxType) { + companion object { + const val SANDBOX_STRING = "sandbox.java.lang.String" + + @JvmField + val DETERMINISTIC_RT: Path = Paths.get( + System.getProperty("deterministic-rt.path") ?: fail("deterministic-rt.path property not set")) + + @JvmField + val TESTING_LIBRARIES: List = (System.getProperty("sandbox-libraries.path") + ?: fail("sandbox-libraries.path property not set")) + .split(File.pathSeparator).map { Paths.get(it) }.filter { exists(it) } + + private lateinit var bootstrapClassLoader: BootstrapClassLoader + private lateinit var parentConfiguration: SandboxConfiguration + + @BeforeAll + @JvmStatic + fun setupClassLoader() { + bootstrapClassLoader = BootstrapClassLoader(DETERMINISTIC_RT) + val rootConfiguration = AnalysisConfiguration.createRoot( + userSource = UserPathSource(emptyList()), + whitelist = MINIMAL, + visibleAnnotations = setOf( + CordaSerializable::class.java, + ConstructorForDeserialization::class.java, + DeprecatedConstructorForDeserialization::class.java + ), + bootstrapSource = bootstrapClassLoader + ) + parentConfiguration = SandboxConfiguration.createFor( + analysisConfiguration = rootConfiguration, + profile = UNLIMITED, + enableTracing = false + ) + } + + @AfterAll + @JvmStatic + fun destroyRootContext() { + bootstrapClassLoader.close() + } + } + + val classPaths: List = when(type) { + SandboxType.KOTLIN -> TESTING_LIBRARIES + SandboxType.JAVA -> TESTING_LIBRARIES.filter { isDirectory(it) } + } + + fun sandbox(action: SandboxRuntimeContext.() -> Unit) { + return sandbox(WARNING, emptySet(), emptySet(), action) + } + + fun sandbox(visibleAnnotations: Set>, action: SandboxRuntimeContext.() -> Unit) { + return sandbox(WARNING, visibleAnnotations, emptySet(), action) + } + + fun sandbox( + visibleAnnotations: Set>, + sandboxOnlyAnnotations: Set, + action: SandboxRuntimeContext.() -> Unit + ) { + return sandbox(WARNING, visibleAnnotations, sandboxOnlyAnnotations, action) + } + + fun sandbox( + minimumSeverityLevel: Severity, + visibleAnnotations: Set>, + sandboxOnlyAnnotations: Set, + action: SandboxRuntimeContext.() -> Unit + ) { + var thrownException: Throwable? = null + thread(start = false) { + UserPathSource(classPaths).use { userSource -> + SandboxRuntimeContext(parentConfiguration.createChild(userSource, Consumer { + it.withNewMinimumSeverityLevel(minimumSeverityLevel) + .withSandboxOnlyAnnotations(sandboxOnlyAnnotations) + .withVisibleAnnotations(visibleAnnotations) + })).use { + action(this) + } + } + }.apply { + uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { _, ex -> + thrownException = ex + } + start() + join() + } + throw thrownException ?: return + } +} diff --git a/serialization-djvm/src/test/resources/log4j2-test.xml b/serialization-djvm/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..cf527ff319 --- /dev/null +++ b/serialization-djvm/src/test/resources/log4j2-test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/serialization-djvm/src/test/resources/testing.cert b/serialization-djvm/src/test/resources/testing.cert new file mode 100644 index 0000000000..03e1558cca Binary files /dev/null and b/serialization-djvm/src/test/resources/testing.cert differ diff --git a/serialization-djvm/src/test/scripts/generate-certificate.sh b/serialization-djvm/src/test/scripts/generate-certificate.sh new file mode 100755 index 0000000000..4863542cd5 --- /dev/null +++ b/serialization-djvm/src/test/scripts/generate-certificate.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +KEYPASS=deterministic +STOREPASS=deterministic + +rm -f keystore testing.cert + +keytool -keystore keystore -storetype pkcs12 -genkey -dname 'CN=localhost, O=R3, L=London, C=UK' -keyalg RSA -validity 3650 -keypass ${KEYPASS} -storepass ${STOREPASS} +keytool -keystore keystore -storetype pkcs12 -export -keyalg RSA -file testing.cert -keypass ${KEYPASS} -storepass ${STOREPASS} + +rm -f keystore diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt index 21ff815093..023c3dd4a3 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt @@ -121,8 +121,7 @@ class DeserializationInput constructor( logger.trace("deserialize blob scheme=\"${envelope.schema}\"") - clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), - clazz, context)) + doReadObject(envelope, clazz, context) } @Throws(NotSerializableException::class) @@ -133,13 +132,16 @@ class DeserializationInput constructor( ): ObjectAndEnvelope = des { val envelope = getEnvelope(bytes, context.encodingWhitelist) // Now pick out the obj and schema from the envelope. - ObjectAndEnvelope( - clazz.cast(readObjectOrNull( - envelope.obj, - SerializationSchemas(envelope.schema, envelope.transformsSchema), - clazz, - context)), - envelope) + ObjectAndEnvelope(doReadObject(envelope, clazz, context), envelope) + } + + private fun doReadObject(envelope: Envelope, clazz: Class, context: SerializationContext): T { + return clazz.cast(readObjectOrNull( + obj = redescribe(envelope.obj, clazz), + schema = SerializationSchemas(envelope.schema, envelope.transformsSchema), + type = clazz, + context = context + )) } fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, context: SerializationContext @@ -182,7 +184,13 @@ class DeserializationInput constructor( serializer.readObject(obj.described, schemas, this, context) } is Binary -> obj.array - else -> obj // this will be the case for primitive types like [boolean] et al. + else -> if ((type is Class<*>) && type.isPrimitive) { + // this will be the case for primitive types like [boolean] et al. + obj + } else { + // these will be boxed primitive types + serializerFactory.get(obj::class.java, type).readObject(obj, schemas, this, context) + } } // Store the reference in case we need it later on. diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerFactory.kt index 5ef2604c75..dde35db126 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerFactory.kt @@ -42,6 +42,9 @@ class DefaultEvolutionSerializerFactory( private val mustPreserveDataWhenEvolving: Boolean, override val primitiveTypes: Map, Class<*>> ): EvolutionSerializerFactory { + // Invert the "primitive -> boxed primitive" mapping. + private val primitiveBoxedTypes: Map, Class<*>> + = primitiveTypes.entries.associateBy(Map.Entry,Class<*>>::value, Map.Entry,Class<*>>::key) override fun getEvolutionSerializer(remote: RemoteTypeInformation, local: LocalTypeInformation): AMQPSerializer? = @@ -104,13 +107,15 @@ class DefaultEvolutionSerializerFactory( // We have a match if all mandatory evolver properties have a type-compatible property in the remote type. evolverProperties.all { (name, evolverProperty) -> val propertyType = propertyTypes[name] - if (propertyType == null) !evolverProperty.isMandatory - else { + if (propertyType == null) { + !evolverProperty.isMandatory + } else { // Check that we can assign the remote property value to its local equivalent. // This includes assigning a primitive type to its equivalent "boxed" type. val evolverPropertyType = evolverProperty.type.observedType.asClass() evolverPropertyType.isAssignableFrom(propertyType) - || (propertyType.isPrimitive && evolverPropertyType.isAssignableFrom(propertyType.kotlin.javaObjectType)) + || (propertyType.isPrimitive && evolverPropertyType.isAssignableFrom(primitiveBoxedTypes[propertyType] + ?: throw IllegalStateException("Unknown primitive type '$propertyType'"))) } } } diff --git a/settings.gradle b/settings.gradle index d9f6c9f20f..28c395e119 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,7 @@ include 'docs' include 'node-api' include 'node' include 'node:capsule' +include 'node:djvm' include 'client:jackson' include 'client:jfx' include 'client:mock' @@ -77,6 +78,8 @@ include 'samples:cordapp-configuration:workflows' include 'samples:network-verifier:contracts' include 'samples:network-verifier:workflows' include 'serialization' +include 'serialization-djvm' +include 'serialization-djvm:deserializers' include 'serialization-tests' // Common libraries - start diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index a5085085d4..e01a9e0733 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -199,7 +199,9 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr networkParameters = defaultParameters.networkParameters, notaryCustomOverrides = defaultParameters.notaryCustomOverrides, inMemoryDB = defaultParameters.inMemoryDB, - cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes) + cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes), + djvmBootstrapSource = defaultParameters.djvmBootstrapSource, + djvmCordaSource = defaultParameters.djvmCordaSource ), coerce = { it }, dsl = dsl @@ -236,6 +238,8 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr * the data is not persisted between node restarts). Has no effect if node is configured * in any way to use database other than H2. * @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [DriverDSL]. + * @property djvmBootstrapSource Location of a JAR containing the Java APIs for the DJVM to use. + * @property djvmCordaSource Locations of JARs of user-supplied classes to execute within the DJVM sandbox. */ @Suppress("unused") data class DriverParameters( @@ -255,10 +259,51 @@ data class DriverParameters( val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), val notaryCustomOverrides: Map = emptyMap(), val inMemoryDB: Boolean = true, - val cordappsForAllNodes: Collection? = null + val cordappsForAllNodes: Collection? = null, + val djvmBootstrapSource: Path? = null, + val djvmCordaSource: List = emptyList() ) { constructor(cordappsForAllNodes: Collection) : this(isDebug = false, cordappsForAllNodes = cordappsForAllNodes) + // Legacy constructor from v4.3 + constructor( + isDebug: Boolean = false, + driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(), + portAllocation: PortAllocation = incrementalPortAllocation(), + debugPortAllocation: PortAllocation = incrementalPortAllocation(), + systemProperties: Map = emptyMap(), + useTestClock: Boolean = false, + startNodesInProcess: Boolean = false, + waitForAllNodesToFinish: Boolean = false, + notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY_NAME)), + extraCordappPackagesToScan: List = emptyList(), + @Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(), + networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), + notaryCustomOverrides: Map = emptyMap(), + inMemoryDB: Boolean = true, + cordappsForAllNodes: Collection? = null + ) : this( + isDebug, + driverDirectory, + portAllocation, + debugPortAllocation, + systemProperties, + useTestClock, + startNodesInProcess, + waitForAllNodesToFinish, + notarySpecs, + extraCordappPackagesToScan, + jmxPolicy, + networkParameters, + notaryCustomOverrides, + inMemoryDB, + cordappsForAllNodes, + + // These fields have been added in v4.4 + djvmBootstrapSource = null, + djvmCordaSource = emptyList() + ) + constructor( isDebug: Boolean = false, driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(), @@ -373,6 +418,8 @@ data class DriverParameters( fun withNotaryCustomOverrides(notaryCustomOverrides: Map): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides) fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB) fun withCordappsForAllNodes(cordappsForAllNodes: Collection?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes) + fun withDjvmBootstrapSource(djvmBootstrapSource: Path?): DriverParameters = copy(djvmBootstrapSource = djvmBootstrapSource) + fun withDjvmCordaSource(djvmCordaSource: List): DriverParameters = copy(djvmCordaSource = djvmCordaSource) fun copy( isDebug: Boolean, @@ -433,4 +480,44 @@ data class DriverParameters( notaryCustomOverrides = emptyMap(), cordappsForAllNodes = cordappsForAllNodes ) + + // Legacy copy() from v4.3 + @Suppress("LongParameterList") + fun copy( + isDebug: Boolean, + driverDirectory: Path, + portAllocation: PortAllocation, + debugPortAllocation: PortAllocation, + systemProperties: Map, + useTestClock: Boolean, + startNodesInProcess: Boolean, + waitForAllNodesToFinish: Boolean, + notarySpecs: List, + extraCordappPackagesToScan: List, + jmxPolicy: JmxPolicy, + networkParameters: NetworkParameters, + notaryCustomOverrides: Map, + inMemoryDB: Boolean, + cordappsForAllNodes: Collection? + ) = this.copy( + isDebug = isDebug, + driverDirectory = driverDirectory, + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + useTestClock = useTestClock, + startNodesInProcess = startNodesInProcess, + waitForAllNodesToFinish = waitForAllNodesToFinish, + notarySpecs = notarySpecs, + extraCordappPackagesToScan = extraCordappPackagesToScan, + jmxPolicy = jmxPolicy, + networkParameters = networkParameters, + notaryCustomOverrides = notaryCustomOverrides, + inMemoryDB = inMemoryDB, + cordappsForAllNodes = cordappsForAllNodes, + + // These fields have been added in v4.4 + djvmBootstrapSource = djvmBootstrapSource, + djvmCordaSource = djvmCordaSource + ) } \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 01bd936cbe..dde4af7310 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -490,4 +490,4 @@ fun createMockCordaService(serviceHub: MockServices, serv } } return MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance -} \ No newline at end of file +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 6d3fa042ea..37a3a146c6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -5,14 +5,30 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigValue import com.typesafe.config.ConfigValueFactory import net.corda.client.rpc.CordaRPCClient import net.corda.cliutils.CommonCliConstants.BASE_DIR import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.* -import net.corda.core.internal.concurrent.* +import net.corda.core.internal.PLATFORM_VERSION +import net.corda.core.internal.ThreadBox +import net.corda.core.internal.concurrent.doOnError +import net.corda.core.internal.concurrent.doneFuture +import net.corda.core.internal.concurrent.flatMap +import net.corda.core.internal.concurrent.fork +import net.corda.core.internal.concurrent.map +import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.concurrent.transpose +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.list +import net.corda.core.internal.packageName_ +import net.corda.core.internal.readObject +import net.corda.core.internal.toPath +import net.corda.core.internal.uncheckedCast +import net.corda.core.internal.writeText import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NetworkParameters import net.corda.core.node.NotaryInfo @@ -27,7 +43,16 @@ import net.corda.node.internal.Node import net.corda.node.internal.NodeWithInfo import net.corda.node.internal.clientSslOptionsCompatibleWith import net.corda.node.services.Permissions -import net.corda.node.services.config.* +import net.corda.node.services.config.ConfigHelper +import net.corda.node.services.config.FlowOverride +import net.corda.node.services.config.FlowOverrideConfig +import net.corda.node.services.config.NetworkServicesConfig +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.NotaryConfig +import net.corda.node.services.config.configOf +import net.corda.node.services.config.configureDevKeyAndTrustStores +import net.corda.node.services.config.parseAsNodeConfiguration +import net.corda.node.services.config.plus import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NodeRegistrationConfiguration import net.corda.node.utilities.registration.NodeRegistrationHelper @@ -63,7 +88,8 @@ import java.time.Duration import java.time.Instant import java.time.ZoneOffset.UTC import java.time.format.DateTimeFormatter -import java.util.* +import java.util.Random +import java.util.UUID import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit @@ -91,7 +117,9 @@ class DriverDSLImpl( val networkParameters: NetworkParameters, val notaryCustomOverrides: Map, val inMemoryDB: Boolean, - val cordappsForAllNodes: Collection? + val cordappsForAllNodes: Collection?, + val djvmBootstrapSource: Path?, + val djvmCordaSource: List ) : InternalDriverDSL { private var _executorService: ScheduledExecutorService? = null @@ -128,7 +156,7 @@ class DriverDSLImpl( if (inMemoryDB && corda.dataSourceProperties.getProperty("dataSource.url").startsWith("jdbc:h2:")) { val jdbcUrl = "jdbc:h2:mem:persistence${inMemoryCounter.getAndIncrement()};DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100" corda.dataSourceProperties.setProperty("dataSource.url", jdbcUrl) - NodeConfig(typesafe = typesafe + mapOf("dataSourceProperties" to mapOf("dataSource.url" to jdbcUrl))) + NodeConfig(typesafe + mapOf("dataSourceProperties" to mapOf("dataSource.url" to jdbcUrl))) } else { this } @@ -235,11 +263,13 @@ class DriverDSLImpl( NodeConfiguration::flowOverrides.name to flowOverrideConfig.toConfig().root().unwrapped(), NodeConfiguration::additionalNodeInfoPollingFrequencyMsec.name to 1000 ) + czUrlConfig + jmxConfig + parameters.customOverrides - val config = NodeConfig(ConfigHelper.loadConfig( + val config = NodeConfig( + ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) - )).checkAndOverrideForInMemoryDB() + ).withDJVMConfig(djvmBootstrapSource, djvmCordaSource) + ).checkAndOverrideForInMemoryDB() return startNodeInternal(config, webAddress, localNetworkMap, parameters) } @@ -260,11 +290,13 @@ class DriverDSLImpl( ), "additionalNodeInfoPollingFrequencyMsec" to 1000, "devMode" to false) + customOverrides - val config = NodeConfig(ConfigHelper.loadConfig( + val config = NodeConfig( + ConfigHelper.loadConfig( baseDirectory = baseDirectory, allowMissingConfig = true, configOverrides = overrides - )).checkAndOverrideForInMemoryDB() + ).withDJVMConfig(djvmBootstrapSource, djvmCordaSource) + ).checkAndOverrideForInMemoryDB() val versionInfo = VersionInfo(PLATFORM_VERSION, "1", "1", "1") config.corda.certificatesDirectory.createDirectories() @@ -699,6 +731,30 @@ class DriverDSLImpl( Permissions.invokeRpc(CordaRPCOps::killFlow) ) + /** + * Add the DJVM's sources to the node's configuration file. + * These will all be ignored unless devMode is also true. + */ + private fun Config.withDJVMConfig(bootstrapSource: Path?, cordaSource: List): Config { + return if (hasPath("devMode") && getBoolean("devMode")) { + withOptionalValue("devModeOptions.djvm.bootstrapSource", bootstrapSource) { path -> + valueFor(path.toString()) + }.withValue("devModeOptions.djvm.cordaSource", valueFor(cordaSource.map(Path::toString))) + } else { + this + } + } + + private inline fun Config.withOptionalValue(key: String, obj: T?, body: (T) -> ConfigValue): Config { + return if (obj == null) { + this + } else { + withValue(key, body(obj)) + } + } + + private fun valueFor(any: T): ConfigValue = ConfigValueFactory.fromAnyRef(any) + private fun oneOf(array: Array) = array[Random().nextInt(array.size)] private fun startInProcessNode( @@ -752,16 +808,18 @@ class DriverDSLImpl( systemProperties += overriddenSystemProperties // See experimental/quasar-hook/README.md for how to generate. - val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + + val excludePackagePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + "com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" + "com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" + - "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" + + "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.corda.djvm**;djvm.**;net.bytebuddy**;" + + "net.i2p**;org.apache**;" + "org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" + "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" + "com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;)" + val excludeClassloaderPattern = "l(net.corda.djvm.**)" val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + - "-javaagent:$quasarJarPath=$excludePattern" + "-javaagent:$quasarJarPath=$excludePackagePattern$excludeClassloaderPattern" val loggingLevel = when { logLevelOverride != null -> logLevelOverride @@ -777,8 +835,10 @@ class DriverDSLImpl( it += extraCmdLineFlag }.toList() - // The following dependencies are excluded from the classpath of the created JVM, so that the environment resembles a real one as close as possible. - // These are either classes that will be added as attachments to the node (i.e. samples, finance, opengamma etc.) or irrelevant testing libraries (test, corda-mock etc.). + // The following dependencies are excluded from the classpath of the created JVM, + // so that the environment resembles a real one as close as possible. + // These are either classes that will be added as attachments to the node (i.e. samples, finance, opengamma etc.) + // or irrelevant testing libraries (test, corda-mock etc.). // TODO: There is pending work to fix this issue without custom blacklisting. See: https://r3-cev.atlassian.net/browse/CORDA-2164. val exclude = listOf("samples", "finance", "integrationTest", "test", "corda-mock", "com.opengamma.strata") val cp = ProcessUtilities.defaultClassPath.filterNot { cpEntry -> @@ -1013,7 +1073,9 @@ fun genericDriver( networkParameters = defaultParameters.networkParameters, notaryCustomOverrides = defaultParameters.notaryCustomOverrides, inMemoryDB = defaultParameters.inMemoryDB, - cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes) + cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes), + djvmBootstrapSource = defaultParameters.djvmBootstrapSource, + djvmCordaSource = defaultParameters.djvmCordaSource ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -1090,6 +1152,7 @@ class SplitCompatibilityZoneParams( override fun config() : NetworkServicesConfig = config } +@Suppress("LongParameterList") fun internalDriver( isDebug: Boolean = DriverParameters().isDebug, driverDirectory: Path = DriverParameters().driverDirectory, @@ -1107,6 +1170,8 @@ fun internalDriver( notaryCustomOverrides: Map = DriverParameters().notaryCustomOverrides, inMemoryDB: Boolean = DriverParameters().inMemoryDB, cordappsForAllNodes: Collection? = null, + djvmBootstrapSource: Path? = null, + djvmCordaSource: List = emptyList(), dsl: DriverDSLImpl.() -> A ): A { return genericDriver( @@ -1126,7 +1191,9 @@ fun internalDriver( networkParameters = networkParameters, notaryCustomOverrides = notaryCustomOverrides, inMemoryDB = inMemoryDB, - cordappsForAllNodes = cordappsForAllNodes + cordappsForAllNodes = cordappsForAllNodes, + djvmBootstrapSource = djvmBootstrapSource, + djvmCordaSource = djvmCordaSource ), coerce = { it }, dsl = dsl diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index c1caf61551..53da93b860 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -104,6 +104,7 @@ val fakeNodeLegalName = CordaX500Name(organisation = "Not:a:real:name", locality private val globalPortAllocation = incrementalPortAllocation() private val globalDebugPortAllocation = incrementalPortAllocation() +@Suppress("LongParameterList") fun rpcDriver( isDebug: Boolean = false, driverDirectory: Path = Paths.get("build") / "rpc-driver" / getTimestampAsDirectoryName(), @@ -121,6 +122,8 @@ fun rpcDriver( notaryCustomOverrides: Map = emptyMap(), inMemoryDB: Boolean = true, cordappsForAllNodes: Collection? = null, + djvmBootstrapSource: Path? = null, + djvmCordaSource: List = emptyList(), dsl: RPCDriverDSL.() -> A ): A { return genericDriver( @@ -141,7 +144,9 @@ fun rpcDriver( networkParameters = networkParameters, notaryCustomOverrides = notaryCustomOverrides, inMemoryDB = inMemoryDB, - cordappsForAllNodes = cordappsForAllNodes + cordappsForAllNodes = cordappsForAllNodes, + djvmBootstrapSource = djvmBootstrapSource, + djvmCordaSource = djvmCordaSource ), externalTrace ), coerce = { it }, diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt index 5ae40f6eb4..a3f329d101 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt @@ -1,7 +1,11 @@ package net.corda.demobench.model -import com.typesafe.config.* +import com.typesafe.config.Config import com.typesafe.config.ConfigFactory.empty +import com.typesafe.config.ConfigObject +import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigValue +import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.copyToDirectory @@ -12,7 +16,7 @@ import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.toConfig import java.nio.file.Path import java.nio.file.StandardCopyOption -import java.util.* +import java.util.Properties /** * This is a subset of FullNodeConfiguration, containing only those configs which we need. The node uses reference.conf @@ -38,6 +42,10 @@ data class NodeConfig( ) { companion object { val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false) + val systemProperties: Map = mapOf( + "net.corda.djvm" to true, + "co.paralleluniverse.fibers.verifyInstrumentation" to false + ) val defaultUser = user("guest") const val CORDAPP_DIR_NAME = "cordapps" } @@ -48,8 +56,18 @@ data class NodeConfig( .withValue("address", valueFor(rpcSettings.address.toString())) .withValue("adminAddress", valueFor(rpcSettings.adminAddress.toString())) .root() - return NodeConfigurationData(myLegalName, p2pAddress, this.rpcSettings.address, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode) + return NodeConfigurationData( + myLegalName = myLegalName, + p2pAddress = p2pAddress, + rpcAddress = this.rpcSettings.address, + notary = notary, + h2port = h2port, + rpcUsers = rpcUsers, + useTestClock = useTestClock, + detectPublicIp = detectPublicIp, + devMode = devMode) .toConfig() + .withValue("systemProperties", valueFor(systemProperties)) .withoutPath("rpcAddress") .withoutPath("rpcAdminAddress") .withValue("rpcSettings", rpcSettings) diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index c71dbeb948..818fe7d184 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -6,6 +6,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.config.toProperties import net.corda.webserver.WebServerConfig import org.junit.Test import java.nio.file.Path @@ -40,10 +41,12 @@ class NodeConfigTest { .withFallback(ConfigFactory.parseMap(mapOf("devMode" to true))) .resolve() val fullConfig = nodeConfig.parseAsNodeConfiguration().value() + val systemProperties = nodeConfig.getConfig("systemProperties").toProperties() // No custom configuration is created by default. assertFailsWith { nodeConfig.getConfig("custom") } - assertFailsWith { nodeConfig.getConfig("systemProperties") } + assertEquals(true.toString(), systemProperties.getProperty("net.corda.djvm")) + assertEquals(false.toString(), systemProperties.getProperty("co.paralleluniverse.fibers.verifyInstrumentation")) assertEquals(myLegalName, fullConfig.myLegalName) assertEquals(localPort(40002), fullConfig.rpcOptions.address) @@ -67,7 +70,7 @@ class NodeConfigTest { ) val nodeConfig = config.nodeConf() - .withValue("systemProperties", valueFor(mapOf("property" to "value"))) + .withValue("systemProperties", valueFor(mapOf("property.name" to "value"))) .withValue("custom.jvmArgs", valueFor("-Xmx1000G")) .withValue("baseDirectory", valueFor(baseDir.toString())) .withFallback(ConfigFactory.parseResources("reference.conf")) @@ -76,7 +79,7 @@ class NodeConfigTest { val fullConfig = nodeConfig.parseAsNodeConfiguration().value() assertEquals("-Xmx1000G", nodeConfig.getConfig("custom").getString("jvmArgs")) - assertEquals("value", nodeConfig.getConfig("systemProperties").getString("property")) + assertEquals("value", nodeConfig.getConfig("systemProperties").toProperties().getProperty("property.name")) assertEquals(myLegalName, fullConfig.myLegalName) assertEquals(localPort(40002), fullConfig.rpcOptions.address)