CORDA-3523: DJVM custom serializers and whitelists (#5858)

This commit is contained in:
Chris Rankin 2020-01-16 17:48:15 +00:00 committed by Rick Parker
parent f027fd5c77
commit 01666ed068
55 changed files with 990 additions and 68 deletions

View File

@ -30,8 +30,8 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0
metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1
djvmVersion=1.0-RC03
deterministicRtVersion=1.0-RC01
djvmVersion=1.0-RC04
deterministicRtVersion=1.0-RC02
openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4
jolokiaAgentVersion=1.6.1

View File

@ -19,6 +19,20 @@ import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.a
*/
@StubOutForDJVM
fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
return getNamesOfClassesImplementing(classloader, clazz)
.map { classloader.loadClass(it).asSubclass(clazz) }
.mapTo(LinkedHashSet()) { it.kotlin.objectOrNewInstance() }
}
/**
* Scans for all the non-abstract classes in the classpath of the provided classloader which implement the interface of the provided class.
* @param classloader the classloader, which will be searched for the classes.
* @param clazz the class of the interface, which the classes - to be returned - must implement.
*
* @return names of the identified classes.
*/
@StubOutForDJVM
fun <T: Any> getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<String> {
return ClassGraph().overrideClassLoaders(classloader)
.enableURLScheme(attachmentScheme)
.ignoreParentClassLoaders()
@ -27,8 +41,7 @@ fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, claz
.use { result ->
result.getClassesImplementing(clazz.name)
.filterNot(ClassInfo::isAbstract)
.map { classloader.loadClass(it.name).asSubclass(clazz) }
.mapTo(LinkedHashSet()) { it.kotlin.objectOrNewInstance() }
.mapTo(LinkedHashSet(), ClassInfo::getName)
}
}

View File

@ -17,7 +17,7 @@ dependencies {
jdk "net.corda:deterministic-rt:$deterministic_rt_version:api"
}
task copyJdk(type: Copy) {
def copyJdk = tasks.register('copyJdk', Copy) {
outputs.dir jdk_home
from(configurations.jdk) {
@ -38,7 +38,9 @@ task copyJdk(type: Copy) {
}
assemble.dependsOn copyJdk
jar.enabled = false
tasks.named('jar', Jar) {
enabled = false
}
artifacts {
jdk file: file(rt_jar), type: 'jar', builtBy: copyJdk

View File

@ -19,7 +19,7 @@ class SandboxAttachmentContract : Contract {
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 ->
tx.commandsOfType<ExtractFile>().forEach { extract ->
val fileName = extract.value.fileName
val contents = ByteArrayOutputStream().use {
attachment.extractFile(fileName, it)

View File

@ -11,11 +11,11 @@ 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()
tx.commandsOfType<InstantNow>().isNotEmpty() -> verifyInstantNow()
tx.commandsOfType<CurrentTimeMillis>().isNotEmpty() -> verifyCurrentTimeMillis()
tx.commandsOfType<NanoTime>().isNotEmpty() -> verifyNanoTime()
tx.commandsOfType<RandomUUID>().isNotEmpty() -> UUID.randomUUID()
tx.commandsOfType<WithReflection>().isNotEmpty() -> verifyNoReflection()
else -> {}
}
}

View File

@ -11,8 +11,8 @@ 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 cryptoData = tx.outputsOfType<CryptoState>()
val validators = tx.commandsOfType<Validate>()
val isValid = validators.all { validate ->
with (validate.value) {

View File

@ -0,0 +1,38 @@
package net.corda.contracts.djvm.whitelist
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
class DeterministicWhitelistContract : Contract {
companion object {
const val MAX_VALUE = 2000L
}
override fun verify(tx: LedgerTransaction) {
val states = tx.outputsOfType<State>()
require(states.isNotEmpty()) {
"Requires at least one custom data state"
}
states.forEach {
require(it.whitelistData in WhitelistData(0)..WhitelistData(MAX_VALUE)) {
"WhitelistData $it exceeds maximum value!"
}
}
}
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
class State(val owner: AbstractParty, val whitelistData: WhitelistData) : ContractState {
override val participants: List<AbstractParty> = listOf(owner)
@Override
override fun toString(): String {
return whitelistData.toString()
}
}
class Operate : CommandData
}

View File

@ -0,0 +1,15 @@
package net.corda.contracts.djvm.whitelist
import net.corda.core.serialization.SerializationWhitelist
data class WhitelistData(val value: Long) : Comparable<WhitelistData> {
override fun compareTo(other: WhitelistData): Int {
return value.compareTo(other.value)
}
override fun toString(): String = "$value things"
}
class Whitelist : SerializationWhitelist {
override val whitelist = listOf(WhitelistData::class.java)
}

View File

@ -0,0 +1,28 @@
package net.corda.flows.djvm.whitelist
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.djvm.whitelist.DeterministicWhitelistContract.Operate
import net.corda.contracts.djvm.whitelist.DeterministicWhitelistContract.State
import net.corda.contracts.djvm.whitelist.WhitelistData
import net.corda.core.contracts.Command
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 DeterministicWhitelistFlow(private val data: WhitelistData) : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val stx = serviceHub.signInitialTransaction(
TransactionBuilder(notary)
.addOutputState(State(ourIdentity, data))
.addCommand(Command(Operate(), ourIdentity.owningKey))
)
stx.verify(serviceHub, checkSufficientSignatures = false)
return stx.id
}
}

View File

@ -26,7 +26,7 @@ import java.security.KeyPairGenerator
class DeterministicContractCryptoTest {
companion object {
const val MESSAGE = "Very Important Data! Do Not Change!"
val logger = loggerFor<NonDeterministicContractVerifyTest>()
val logger = loggerFor<DeterministicContractCryptoTest>()
@ClassRule
@JvmField

View File

@ -0,0 +1,89 @@
package net.corda.node.services
import net.corda.contracts.serialization.custom.Currantsy
import net.corda.contracts.serialization.custom.CustomSerializerContract
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.flows.serialization.custom.CustomSerializerFlow
import net.corda.node.DeterministicSourcesRule
import net.corda.node.assertNotCordaSerializable
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.TestCordapp
import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
@Suppress("FunctionName")
class DeterministicContractWithCustomSerializerTest {
companion object {
val logger = loggerFor<DeterministicContractWithCustomSerializerTest>()
const val GOOD_CURRANTS = 1201L
const val BAD_CURRANTS = 4703L
@ClassRule
@JvmField
val djvmSources = DeterministicSourcesRule()
@JvmField
val flowCordapp = cordappWithPackages("net.corda.flows.serialization.custom").signed()
@JvmField
val contractCordapp = cordappWithPackages("net.corda.contracts.serialization.custom").signed()
fun parametersFor(djvmSources: DeterministicSourcesRule, vararg cordapps: TestCordapp): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
cordappsForAllNodes = cordapps.toList(),
djvmBootstrapSource = djvmSources.bootstrap,
djvmCordaSource = djvmSources.corda
)
}
@BeforeClass
@JvmStatic
fun checkData() {
assertNotCordaSerializable<Currantsy>()
}
}
@Test
fun `test DJVM can verify using custom serializer`() {
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val txId = assertDoesNotThrow {
alice.rpc.startFlow(::CustomSerializerFlow, Currantsy(GOOD_CURRANTS))
.returnValue.getOrThrow()
}
logger.info("TX-ID: {}", txId)
}
}
@Test
fun `test DJVM can fail verify using custom serializer`() {
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val currantsy = Currantsy(BAD_CURRANTS)
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::CustomSerializerFlow, currantsy)
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("sandbox.net.corda.core.contracts.TransactionVerificationException\$ContractRejection -> ")
.hasMessageContaining(" Contract verification failed: Too many currants! $currantsy is unraisinable!, ")
.hasMessageContaining(" contract: sandbox.${CustomSerializerContract::class.java.name}, ")
}
}
}

View File

@ -0,0 +1,89 @@
package net.corda.node.services
import net.corda.contracts.djvm.whitelist.DeterministicWhitelistContract
import net.corda.contracts.djvm.whitelist.WhitelistData
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.flows.djvm.whitelist.DeterministicWhitelistFlow
import net.corda.node.DeterministicSourcesRule
import net.corda.node.assertNotCordaSerializable
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.TestCordapp
import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
@Suppress("FunctionName")
class DeterministicContractWithSerializationWhitelistTest {
companion object {
val logger = loggerFor<DeterministicContractWithSerializationWhitelistTest>()
const val GOOD_VALUE = 1201L
const val BAD_VALUE = 6333L
@ClassRule
@JvmField
val djvmSources = DeterministicSourcesRule()
@JvmField
val flowCordapp = cordappWithPackages("net.corda.flows.djvm.whitelist").signed()
@JvmField
val contractCordapp = cordappWithPackages("net.corda.contracts.djvm.whitelist").signed()
fun parametersFor(djvmSources: DeterministicSourcesRule, vararg cordapps: TestCordapp): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
cordappsForAllNodes = cordapps.toList(),
djvmBootstrapSource = djvmSources.bootstrap,
djvmCordaSource = djvmSources.corda
)
}
@BeforeClass
@JvmStatic
fun checkData() {
assertNotCordaSerializable<WhitelistData>()
}
}
@Test
fun `test DJVM can verify using whitelist`() {
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val txId = assertDoesNotThrow {
alice.rpc.startFlow(::DeterministicWhitelistFlow, WhitelistData(GOOD_VALUE))
.returnValue.getOrThrow()
}
logger.info("TX-ID: {}", txId)
}
}
@Test
fun `test DJVM can fail verify using whitelist`() {
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val badData = WhitelistData(BAD_VALUE)
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::DeterministicWhitelistFlow, badData)
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("sandbox.net.corda.core.contracts.TransactionVerificationException\$ContractRejection -> ")
.hasMessageContaining(" Contract verification failed: WhitelistData $badData exceeds maximum value!, ")
.hasMessageContaining(" contract: sandbox.${DeterministicWhitelistContract::class.java.name}, ")
}
}
}

View File

@ -9,6 +9,9 @@ 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.internal.getNamesOfClassesImplementing
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.djvm.SandboxConfiguration
@ -20,14 +23,33 @@ import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.djvm.source.ClassSource
import net.corda.node.djvm.LtxFactory
import java.util.function.Function
import kotlin.collections.LinkedHashSet
class DeterministicVerifier(
ltx: LedgerTransaction,
transactionClassLoader: ClassLoader,
private val sandboxConfiguration: SandboxConfiguration
) : Verifier(ltx, transactionClassLoader) {
/**
* Read the whitelisted classes without using the [java.util.ServiceLoader] mechanism
* because the whitelists themselves are untrusted.
*/
private fun getSerializationWhitelistNames(classLoader: ClassLoader): Set<String> {
return classLoader.getResources("META-INF/services/${SerializationWhitelist::class.java.name}").asSequence()
.flatMapTo(LinkedHashSet()) { url ->
url.openStream().bufferedReader().useLines { lines ->
// Parse file format, as documented for java.util.ServiceLoader:
// - Remove everything after comment character '#'.
// - Strip whitespace.
// - Ignore empty lines.
lines.map { it.substringBefore('#') }.map(String::trim).filterNot(String::isEmpty).toList()
}.asSequence()
}
}
override fun verifyContracts() {
val customSerializerNames = getNamesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
val serializationWhitelistNames = getSerializationWhitelistNames(transactionClassLoader)
val result = IsolatedTask(ltx.id.toString(), sandboxConfiguration).run<Any>(Function { classLoader ->
(classLoader.parent as? SandboxClassLoader)?.apply {
/**
@ -49,7 +71,7 @@ class DeterministicVerifier(
* that we can execute inside the DJVM's sandbox.
*/
val sandboxTx = ltx.transform { componentGroups, serializedInputs, serializedReferences ->
val serializer = Serializer(classLoader)
val serializer = Serializer(classLoader, customSerializerNames, serializationWhitelistNames)
val componentFactory = ComponentFactory(
classLoader,
taskFactory,

View File

@ -11,12 +11,16 @@ import net.corda.node.djvm.ComponentBuilder
import net.corda.serialization.djvm.createSandboxSerializationEnv
import java.util.function.Function
class Serializer(private val classLoader: SandboxClassLoader) {
class Serializer(
private val classLoader: SandboxClassLoader,
customSerializerNames: Set<String>,
serializationWhitelists: Set<String>
) {
private val factory: SerializationFactory
private val context: SerializationContext
init {
val env = createSandboxSerializationEnv(classLoader)
val env = createSandboxSerializationEnv(classLoader, customSerializerNames, serializationWhitelists)
factory = env.serializationFactory
context = env.p2pContext
}

View File

@ -46,8 +46,7 @@ class DeterministicVerifierFactoryService(
baseSandboxConfiguration = SandboxConfiguration.createFor(
analysisConfiguration = baseAnalysisConfiguration,
profile = NODE_PROFILE,
enableTracing = true
profile = NODE_PROFILE
)
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import net.corda.core.serialization.SerializationCustomSerializer
import java.util.function.Function
class CorDappCustomDeserializer(private val serializer: SerializationCustomSerializer<Any?, Any?>) : Function<Any?, Any?> {
override fun apply(input: Any?): Any? {
return serializer.fromProxy(input)
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import net.corda.core.serialization.SerializationWhitelist
import java.util.function.Function
class MergeWhitelists : Function<Array<SerializationWhitelist>, Array<Class<*>>> {
override fun apply(whitelists: Array<SerializationWhitelist>): Array<Class<*>> {
return whitelists.flatMapTo(LinkedHashSet(), SerializationWhitelist::whitelist).toTypedArray()
}
}

View File

@ -1,14 +1,18 @@
package net.corda.serialization.djvm
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.deserializers.MergeWhitelists
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.SandboxCorDappCustomSerializer
import net.corda.serialization.djvm.serializers.SandboxCurrencySerializer
import net.corda.serialization.djvm.serializers.SandboxDecimal128Serializer
import net.corda.serialization.djvm.serializers.SandboxDecimal32Serializer
@ -48,6 +52,7 @@ 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.addToWhitelist
import net.corda.serialization.internal.amqp.amqpMagic
import java.math.BigDecimal
import java.math.BigInteger
@ -59,6 +64,8 @@ class AMQPSerializationScheme(
private val classLoader: SandboxClassLoader,
private val sandboxBasicInput: Function<in Any?, out Any?>,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val customSerializerClassNames: Set<String>,
private val serializationWhitelistNames: Set<String>,
private val serializerFactoryFactory: SerializerFactoryFactory
) : SerializationScheme {
@ -112,9 +119,33 @@ class AMQPSerializationScheme(
register(SandboxDecimal64Serializer(classLoader, taskFactory))
register(SandboxDecimal32Serializer(classLoader, taskFactory))
register(SandboxSymbolSerializer(classLoader, taskFactory, sandboxBasicInput))
for (customSerializerName in customSerializerClassNames) {
register(SandboxCorDappCustomSerializer(customSerializerName, classLoader, taskFactory, this))
}
registerWhitelists(this)
}
}
private fun registerWhitelists(factory: SerializerFactory) {
if (serializationWhitelistNames.isEmpty()) {
return
}
val serializationWhitelists = serializationWhitelistNames.map { whitelistClass ->
classLoader.toSandboxClass(whitelistClass).kotlin.objectOrNewInstance()
}.toArrayOf(classLoader.toSandboxClass(SerializationWhitelist::class.java))
@Suppress("unchecked_cast")
val mergeTask = classLoader.createTaskFor(taskFactory, MergeWhitelists::class.java) as Function<in Array<*>, out Array<Class<*>>>
factory.addToWhitelist(mergeTask.apply(serializationWhitelists).toSet())
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private fun Collection<*>.toArrayOf(type: Class<*>): Array<*> {
val typedArray = java.lang.reflect.Array.newInstance(type, 0) as Array<*>
return (this as java.util.Collection<*>).toArray(typedArray)
}
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
val serializerFactory = getSerializerFactory(context)
return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context)

View File

@ -23,6 +23,14 @@ inline fun SandboxClassLoader.toSandboxAnyClass(clazz: Class<*>): Class<Any> {
}
fun createSandboxSerializationEnv(classLoader: SandboxClassLoader): SerializationEnvironment {
return createSandboxSerializationEnv(classLoader, emptySet(), emptySet())
}
fun createSandboxSerializationEnv(
classLoader: SandboxClassLoader,
customSerializerClassNames: Set<String>,
serializationWhitelistNames: Set<String>
): SerializationEnvironment {
val p2pContext: SerializationContext = SerializationContextImpl(
preferredSerializationVersion = amqpMagic,
deserializationClassLoader = DelegatingClassLoader(classLoader),
@ -46,6 +54,8 @@ fun createSandboxSerializationEnv(classLoader: SandboxClassLoader): Serializatio
classLoader = classLoader,
sandboxBasicInput = sandboxBasicInput,
taskFactory = taskFactory,
customSerializerClassNames = customSerializerClassNames,
serializationWhitelistNames = serializationWhitelistNames,
serializerFactoryFactory = SandboxSerializerFactoryFactory(primitiveSerializerFactory)
))
}

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxBitSetSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, BitSetDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(BitSet::class.java)
override val deserializationAliases = aliasFor(BitSet::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxCertPathSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, CertPathDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(CertPath::class.java)
override val deserializationAliases = aliasFor(CertPath::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -0,0 +1,93 @@
package net.corda.serialization.djvm.serializers
import com.google.common.reflect.TypeToken
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.deserializers.CorDappCustomDeserializer
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
import net.corda.serialization.internal.amqp.AMQPTypeIdentifiers
import net.corda.serialization.internal.amqp.CORDAPP_TYPE
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.Descriptor
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.ObjectSerializer
import net.corda.serialization.internal.amqp.PROXY_TYPE
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.SerializerFactory
import net.corda.serialization.internal.amqp.typeDescriptorFor
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.Collections.singleton
import java.util.function.Function
class SandboxCorDappCustomSerializer(
private val serializerName: String,
classLoader: SandboxClassLoader,
taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
factory: SerializerFactory
) : CustomSerializer<Any>() {
private val unproxy: Function<in Any?, out Any?>
private val types: List<Type>
init {
val serializationCustomSerializer = classLoader.toSandboxClass(SerializationCustomSerializer::class.java)
val customSerializerClass = classLoader.toSandboxClass(serializerName)
types = customSerializerClass.genericInterfaces
.mapNotNull { it as? ParameterizedType }
.filter { it.rawType == serializationCustomSerializer }
.flatMap { it.actualTypeArguments.toList() }
if (types.size != 2) {
throw AMQPNotSerializableException(
type = SandboxCorDappCustomSerializer::class.java,
msg = "Unable to determine serializer parent types"
)
}
val unproxyTask = classLoader.toSandboxClass(CorDappCustomDeserializer::class.java)
.getConstructor(serializationCustomSerializer)
.newInstance(customSerializerClass.kotlin.objectOrNewInstance())
unproxy = taskFactory.apply(unproxyTask)
}
override val schemaForDocumentation: Schema = Schema(emptyList())
override val type: Type = types[CORDAPP_TYPE]
private val proxySerializer: ObjectSerializer by lazy {
ObjectSerializer.make(factory.getTypeInformation(types[PROXY_TYPE]), factory)
}
private val deserializationAlias: TypeIdentifier get() =
TypeIdentifier.Erased(AMQPTypeIdentifiers.nameForType(type).replace("sandbox.", ""), 0)
override val typeDescriptor: Symbol = typeDescriptorFor(type)
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override val deserializationAliases: Set<TypeIdentifier> = singleton(deserializationAlias)
/**
* For 3rd party plugin serializers we are going to exist on exact type matching. i.e. we will
* not support base class serializers for derived types.
*/
override fun isSerializerFor(clazz: Class<*>): Boolean {
return TypeToken.of(type) == TypeToken.of(clazz)
}
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any {
return unproxy.apply(proxySerializer.readObject(obj, schemas, input, context))!!
}
override fun writeClassInfo(output: SerializationOutput) {
abortReadOnly()
}
override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) {
abortReadOnly()
}
override fun toString(): String = "${this::class.java}($serializerName)"
}

View File

@ -11,7 +11,6 @@ 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
@ -27,7 +26,7 @@ class SandboxCurrencySerializer(
creator = basicInput.andThen(createTask)
}
override val deserializationAliases: Set<Class<*>> = singleton(Currency::class.java)
override val deserializationAliases = aliasFor(Currency::class.java)
override val schemaForDocumentation: Schema = Schema(emptyList())

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxDurationSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, DurationDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(Duration::class.java)
override val deserializationAliases = aliasFor(Duration::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -25,7 +25,7 @@ class SandboxEnumSetSerializer(
SandboxClassSerializer(classLoader, taskFactory, factory)
)
override val deserializationAliases: Set<Class<*>> = singleton(EnumSet::class.java)
override val deserializationAliases = aliasFor(EnumSet::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -12,7 +12,6 @@ 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(
@ -25,7 +24,7 @@ class SandboxInputStreamSerializer(
override val schemaForDocumentation: Schema = Schema(emptyList())
override val deserializationAliases: Set<Class<*>> = singleton(InputStream::class.java)
override val deserializationAliases = aliasFor(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

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxInstantSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, InstantDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(Instant::class.java)
override val deserializationAliases = aliasFor(Instant::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxLocalDateSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, LocalDateDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(LocalDate::class.java)
override val deserializationAliases = aliasFor(LocalDate::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxLocalDateTimeSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, LocalDateTimeDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(LocalDateTime::class.java)
override val deserializationAliases = aliasFor(LocalDateTime::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxLocalTimeSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, LocalTimeDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(LocalTime::class.java)
override val deserializationAliases = aliasFor(LocalTime::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxMonthDaySerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, MonthDayDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(MonthDay::class.java)
override val deserializationAliases = aliasFor(MonthDay::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxOffsetDateTimeSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, OffsetDateTimeDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(OffsetDateTime::class.java)
override val deserializationAliases = aliasFor(OffsetDateTime::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxOffsetTimeSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, OffsetTimeDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(OffsetTime::class.java)
override val deserializationAliases = aliasFor(OffsetTime::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ import net.corda.serialization.djvm.deserializers.OpaqueBytesSubSequenceDeserial
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(
@ -21,7 +20,7 @@ class SandboxOpaqueBytesSubSequenceSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, OpaqueBytesSubSequenceDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(OpaqueBytesSubSequence::class.java)
override val deserializationAliases = aliasFor(OpaqueBytesSubSequence::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -6,7 +6,6 @@ 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
@ -21,7 +20,7 @@ class SandboxOptionalSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, OptionalDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(Optional::class.java)
override val deserializationAliases = aliasFor(Optional::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -7,7 +7,6 @@ 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(
@ -21,7 +20,7 @@ class SandboxPeriodSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, PeriodDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(Period::class.java)
override val deserializationAliases = aliasFor(Period::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -12,7 +12,6 @@ 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(
@ -25,7 +24,7 @@ class SandboxPublicKeySerializer(
override val schemaForDocumentation: Schema = Schema(emptyList())
override val deserializationAliases: Set<Class<*>> = singleton(PublicKey::class.java)
override val deserializationAliases = aliasFor(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

View File

@ -12,7 +12,6 @@ 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(
@ -31,7 +30,7 @@ class SandboxToStringSerializer(
creator = basicInput.andThen(taskFactory.apply(createTask))
}
override val deserializationAliases: Set<Class<*>> = singleton(unsafeClass)
override val deserializationAliases = aliasFor(unsafeClass)
override val schemaForDocumentation: Schema = Schema(emptyList())

View File

@ -25,7 +25,7 @@ class SandboxX509CRLSerializer(
override val schemaForDocumentation: Schema = Schema(emptyList())
override val deserializationAliases: Set<Class<*>> = singleton(X509CRL::class.java)
override val deserializationAliases = aliasFor(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

View File

@ -25,7 +25,7 @@ class SandboxX509CertificateSerializer(
override val schemaForDocumentation: Schema = Schema(emptyList())
override val deserializationAliases: Set<Class<*>> = singleton(X509Certificate::class.java)
override val deserializationAliases = aliasFor(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

View File

@ -21,7 +21,7 @@ class SandboxYearMonthSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, YearMonthDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(YearMonth::class.java)
override val deserializationAliases = aliasFor(YearMonth::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -21,7 +21,7 @@ class SandboxYearSerializer(
) {
private val task = classLoader.createTaskFor(taskFactory, YearDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(Year::class.java)
override val deserializationAliases = aliasFor(Year::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -23,7 +23,7 @@ class SandboxZoneIdSerializer(
override val revealSubclassesInSchema: Boolean = true
override val deserializationAliases: Set<Class<*>> = singleton(ZoneId::class.java)
override val deserializationAliases = aliasFor(ZoneId::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -38,7 +38,7 @@ class SandboxZonedDateTimeSerializer(
}
}
override val deserializationAliases: Set<Class<*>> = singleton(ZonedDateTime::class.java)
override val deserializationAliases = aliasFor(ZonedDateTime::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()

View File

@ -0,0 +1,8 @@
@file:JvmName("Serializers")
package net.corda.serialization.djvm.serializers
import net.corda.serialization.internal.model.TypeIdentifier
import java.lang.reflect.Type
import java.util.Collections.singleton
fun aliasFor(type: Type): Set<TypeIdentifier> = singleton(TypeIdentifier.forGenericType(type))

View File

@ -0,0 +1,14 @@
@file:JvmName("Assertions")
package net.corda.serialization.djvm
import net.corda.core.serialization.CordaSerializable
import org.junit.jupiter.api.Assertions.assertNull
inline fun <reified T> assertNotCordaSerializable() {
assertNotCordaSerializable(T::class.java)
}
fun assertNotCordaSerializable(clazz: Class<*>) {
assertNull(clazz.getAnnotation(CordaSerializable::class.java),
"$clazz must NOT be annotated as @CordaSerializable!")
}

View File

@ -0,0 +1,119 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationCustomSerializer
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.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.fail
import java.io.NotSerializableException
import java.util.function.Function
class DeserializeComposedCustomDataTest: TestBase(KOTLIN) {
companion object {
const val MESSAGE = "Hello Sandbox!"
const val BIG_NUMBER = 23823L
@Suppress("unused")
@BeforeAll
@JvmStatic
fun checkData() {
assertNotCordaSerializable<LongAtom>()
assertNotCordaSerializable<StringAtom>()
}
}
@RegisterExtension
@JvmField
val serialization = LocalSerialization(setOf(StringAtomSerializer(), LongAtomSerializer()), emptySet())
@Test
fun `test deserializing composed object`() {
val composed = ComposedData(StringAtom(MESSAGE), LongAtom(BIG_NUMBER))
val data = composed.serialize()
sandbox {
val customSerializers = setOf(
StringAtomSerializer::class.java.name,
LongAtomSerializer::class.java.name
)
_contextSerializationEnv.set(createSandboxSerializationEnv(
classLoader = classLoader,
customSerializerClassNames = customSerializers,
serializationWhitelistNames = emptySet()
))
val sandboxComplex = data.deserializeFor(classLoader)
val taskFactory = classLoader.createRawTaskFactory()
val showComplex = classLoader.createTaskFor(taskFactory, ShowComposedData::class.java)
val result = showComplex.apply(sandboxComplex) ?: fail("Result cannot be null")
assertEquals(SANDBOX_STRING, result::class.java.name)
assertEquals(ShowComposedData().apply(composed), result.toString())
}
}
@Test
fun `test deserialization needs custom serializer`() {
val composed = ComposedData(StringAtom(MESSAGE), LongAtom(BIG_NUMBER))
val data = composed.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
assertThrows<NotSerializableException> { data.deserializeFor(classLoader) }
}
}
class ShowComposedData : Function<ComposedData, String> {
private fun show(atom: Atom<*>): String = atom.toString()
override fun apply(composed: ComposedData): String {
return "Composed: message=${show(composed.message)} and value=${show(composed.value)}"
}
}
class StringAtomSerializer : SerializationCustomSerializer<StringAtom, StringAtomSerializer.Proxy> {
data class Proxy(val value: String)
override fun fromProxy(proxy: Proxy): StringAtom = StringAtom(proxy.value)
override fun toProxy(obj: StringAtom): Proxy = Proxy(obj.atom)
}
class LongAtomSerializer : SerializationCustomSerializer<LongAtom, LongAtomSerializer.Proxy> {
data class Proxy(val value: Long?)
override fun fromProxy(proxy: Proxy): LongAtom = LongAtom(proxy.value)
override fun toProxy(obj: LongAtom): Proxy = Proxy(obj.atom)
}
}
@CordaSerializable
class ComposedData(
val message: StringAtom,
val value: LongAtom
)
interface Atom<T> {
val atom: T
}
abstract class AbstractAtom<T>(initialValue: T) : Atom<T> {
override val atom: T = initialValue
override fun toString(): String {
return "[$atom]"
}
}
/**
* These classes REQUIRE custom serializers because their
* constructor parameters cannot be mapped to properties
* automatically. THIS IS DELIBERATE!
*/
class StringAtom(initialValue: String) : AbstractAtom<String>(initialValue)
class LongAtom(initialValue: Long?) : AbstractAtom<Long?>(initialValue)

View File

@ -0,0 +1,97 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.MissingSerializerException
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.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.fail
import java.util.function.Function
class DeserializeCustomGenericDataTest: TestBase(KOTLIN) {
companion object {
const val MESSAGE = "Hello Sandbox!"
const val BIG_NUMBER = 23823L
@Suppress("unused")
@BeforeAll
@JvmStatic
fun checkData() {
assertNotCordaSerializable<GenericData<*>>()
assertNotCordaSerializable<ComplexGenericData>()
}
}
@RegisterExtension
@JvmField
val serialization = LocalSerialization(setOf(CustomSerializer()), emptySet())
@Test
fun `test deserializing custom generic object`() {
val complex = ComplexGenericData(MESSAGE, BIG_NUMBER)
val data = complex.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(
classLoader = classLoader,
customSerializerClassNames = setOf(CustomSerializer::class.java.name),
serializationWhitelistNames = emptySet()
))
val sandboxComplex = data.deserializeFor(classLoader)
val taskFactory = classLoader.createRawTaskFactory()
val showComplex = classLoader.createTaskFor(taskFactory, ShowComplexData::class.java)
val result = showComplex.apply(sandboxComplex) ?: fail("Result cannot be null")
assertEquals(SANDBOX_STRING, result::class.java.name)
assertEquals(ShowComplexData().apply(complex), result.toString())
}
}
@Test
fun `test deserialization needs custom serializer`() {
val complex = ComplexGenericData(MESSAGE, BIG_NUMBER)
val data = complex.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
assertThrows<MissingSerializerException> { data.deserializeFor(classLoader) }
}
}
class ShowComplexData : Function<ComplexGenericData, String> {
private fun show(generic: GenericData<*>): String = generic.toString()
override fun apply(complex: ComplexGenericData): String {
return "Complex: message=${show(complex.message)} and value=${show(complex.value)}"
}
}
/**
* This class REQUIRES a custom serializer because its
* constructor parameters cannot be mapped to properties
* automatically. THIS IS DELIBERATE!
*/
class ComplexGenericData(msg: String, initialValue: Long?) {
val message = GenericData(msg)
val value = GenericData(initialValue)
}
class GenericData<T>(val data: T) {
override fun toString(): String {
return "[$data]"
}
}
class CustomSerializer : SerializationCustomSerializer<ComplexGenericData, CustomSerializer.Proxy> {
data class Proxy(val message: String, val value: Long?)
override fun fromProxy(proxy: Proxy): ComplexGenericData = ComplexGenericData(proxy.message, proxy.value)
override fun toProxy(obj: ComplexGenericData): Proxy = Proxy(obj.message.data, obj.value.data)
}
}

View File

@ -0,0 +1,88 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.MissingSerializerException
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.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.fail
import java.util.function.Function
class DeserializeWithCustomSerializerTest: TestBase(KOTLIN) {
companion object {
const val MESSAGE = "Hello Sandbox!"
@Suppress("unused")
@BeforeAll
@JvmStatic
fun checkData() {
assertNotCordaSerializable<CustomData>()
}
}
@RegisterExtension
@JvmField
val serialization = LocalSerialization(setOf(CustomSerializer()), emptySet())
@Test
fun `test deserializing custom object`() {
val custom = CustomData(MESSAGE)
val data = custom.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(
classLoader = classLoader,
customSerializerClassNames = setOf(CustomSerializer::class.java.name),
serializationWhitelistNames = emptySet()
))
val sandboxCustom = data.deserializeFor(classLoader)
val taskFactory = classLoader.createRawTaskFactory()
val showCustom = classLoader.createTaskFor(taskFactory, ShowCustomData::class.java)
val result = showCustom.apply(sandboxCustom) ?: fail("Result cannot be null")
assertEquals(custom.value, result.toString())
assertEquals(SANDBOX_STRING, result::class.java.name)
}
}
@Test
fun `test deserialization needs custom serializer`() {
val custom = CustomData(MESSAGE)
val data = custom.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
assertThrows<MissingSerializerException> { data.deserializeFor(classLoader) }
}
}
class ShowCustomData : Function<CustomData, String> {
override fun apply(custom: CustomData): String {
return custom.value
}
}
/**
* This class REQUIRES a custom serializer because its
* constructor parameter cannot be mapped to a property
* automatically. THIS IS DELIBERATE!
*/
class CustomData(initialValue: String) {
// DO NOT MOVE THIS PROPERTY INTO THE CONSTRUCTOR!
val value: String = initialValue
}
class CustomSerializer : SerializationCustomSerializer<CustomData, CustomSerializer.Proxy> {
data class Proxy(val value: String)
override fun fromProxy(proxy: Proxy): CustomData = CustomData(proxy.value)
override fun toProxy(obj: CustomData): Proxy = Proxy(obj.value)
}
}

View File

@ -0,0 +1,75 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationCustomSerializer
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.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.fail
import java.util.function.Function
class DeserializeWithObjectCustomSerializerTest: TestBase(KOTLIN) {
companion object {
const val MESSAGE = "Hello Sandbox!"
@Suppress("unused")
@BeforeAll
@JvmStatic
fun checkData() {
assertNotCordaSerializable<CustomData>()
}
}
@RegisterExtension
@JvmField
val serialization = LocalSerialization(setOf(ObjectCustomSerializer), emptySet())
@Test
fun `test deserializing custom object with object serializer`() {
val custom = CustomData(MESSAGE)
val data = custom.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(
classLoader = classLoader,
customSerializerClassNames = setOf(ObjectCustomSerializer::class.java.name),
serializationWhitelistNames = emptySet()
))
val sandboxCustom = data.deserializeFor(classLoader)
val taskFactory = classLoader.createRawTaskFactory()
val showCustom = classLoader.createTaskFor(taskFactory, ShowCustomData::class.java)
val result = showCustom.apply(sandboxCustom) ?: fail("Result cannot be null")
assertEquals(custom.value, result.toString())
assertEquals(SANDBOX_STRING, result::class.java.name)
}
}
class ShowCustomData : Function<CustomData, String> {
override fun apply(custom: CustomData): String {
return custom.value
}
}
/**
* This class REQUIRES a custom serializer because its
* constructor parameter cannot be mapped to a property
* automatically. THIS IS DELIBERATE!
*/
class CustomData(initialValue: String) {
// DO NOT MOVE THIS PROPERTY INTO THE CONSTRUCTOR!
val value: String = initialValue
}
object ObjectCustomSerializer : SerializationCustomSerializer<CustomData, ObjectCustomSerializer.Proxy> {
data class Proxy(val value: String)
override fun fromProxy(proxy: Proxy): CustomData = CustomData(proxy.value)
override fun toProxy(obj: CustomData): Proxy = Proxy(obj.value)
}
}

View File

@ -0,0 +1,81 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationWhitelist
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.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.fail
import java.io.NotSerializableException
import java.util.function.Function
class DeserializeWithSerializationWhitelistTest: TestBase(KOTLIN) {
companion object {
const val MESSAGE = "Hello Sandbox!"
@Suppress("unused")
@BeforeAll
@JvmStatic
fun checkData() {
assertNotCordaSerializable<CustomData>()
}
}
@RegisterExtension
@JvmField
val serialization = LocalSerialization(emptySet(), setOf(CustomWhitelist))
@Test
fun `test deserializing custom object`() {
val custom = CustomData(MESSAGE)
val data = custom.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(
classLoader = classLoader,
customSerializerClassNames = emptySet(),
serializationWhitelistNames = setOf(CustomWhitelist::class.java.name)
))
val sandboxCustom = data.deserializeFor(classLoader)
val taskFactory = classLoader.createRawTaskFactory()
val showCustom = classLoader.createTaskFor(taskFactory, ShowCustomData::class.java)
val result = showCustom.apply(sandboxCustom) ?: fail("Result cannot be null")
assertEquals(custom.value, result.toString())
assertEquals(SANDBOX_STRING, result::class.java.name)
}
}
@Test
fun `test deserialization needs whitelisting`() {
val custom = CustomData(MESSAGE)
val data = custom.serialize()
sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
val ex = assertThrows<NotSerializableException> { data.deserializeFor(classLoader) }
assertThat(ex).hasMessageContaining(
"Class \"class sandbox.${CustomData::class.java.name}\" is not on the whitelist or annotated with @CordaSerializable."
)
}
}
class ShowCustomData : Function<CustomData, String> {
override fun apply(custom: CustomData): String {
return custom.value
}
}
data class CustomData(val value: String)
object CustomWhitelist : SerializationWhitelist {
override val whitelist: List<Class<*>> = listOf(CustomData::class.java)
}
}

View File

@ -20,7 +20,10 @@ 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 {
class LocalSerialization(
private val customSerializers: Set<SerializationCustomSerializer<*,*>>,
private val serializationWhitelists: Set<SerializationWhitelist>
) : BeforeEachCallback, AfterEachCallback {
private companion object {
private val AMQP_P2P_CONTEXT = SerializationContextImpl(
amqpMagic,
@ -33,6 +36,8 @@ class LocalSerialization : BeforeEachCallback, AfterEachCallback {
)
}
constructor() : this(emptySet(), emptySet())
override fun beforeEach(context: ExtensionContext) {
_contextSerializationEnv.set(createTestSerializationEnv())
}
@ -43,7 +48,7 @@ class LocalSerialization : BeforeEachCallback, AfterEachCallback {
private fun createTestSerializationEnv(): SerializationEnvironment {
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
registerScheme(AMQPSerializationScheme(emptySet(), emptySet(), AccessOrderLinkedHashMap(128)))
registerScheme(AMQPSerializationScheme(customSerializers, serializationWhitelists, AccessOrderLinkedHashMap(128)))
}
return SerializationEnvironment.with(factory, AMQP_P2P_CONTEXT)
}

View File

@ -7,7 +7,6 @@ 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
@ -56,8 +55,7 @@ abstract class TestBase(type: SandboxType) {
)
parentConfiguration = SandboxConfiguration.createFor(
analysisConfiguration = rootConfiguration,
profile = UNLIMITED,
enableTracing = false
profile = null
)
}

View File

@ -62,7 +62,7 @@ class CorDappCustomSerializer(
override val type = types[CORDAPP_TYPE]
val proxyType = types[PROXY_TYPE]
override val typeDescriptor: Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${AMQPTypeIdentifiers.nameForType(type)}")
override val typeDescriptor: Symbol = typeDescriptorFor(type)
val descriptor: Descriptor = Descriptor(typeDescriptor)
private val proxySerializer: ObjectSerializer by lazy {
ObjectSerializer.make(factory.getTypeInformation(proxyType), factory)

View File

@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.model.FingerprintWriter
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
@ -33,7 +34,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
* This custom serializer is also allowed to deserialize these classes. This allows us
* to deserialize objects into completely different types, e.g. `A` -> `sandbox.A`.
*/
open val deserializationAliases: Set<Class<*>> = emptySet()
open val deserializationAliases: Set<TypeIdentifier> = emptySet()
protected abstract val descriptor: Descriptor
/**