mirror of
https://github.com/corda/corda.git
synced 2025-01-31 16:35:43 +00:00
CORDA-3523: DJVM custom serializers and whitelists (#5858)
This commit is contained in:
parent
f027fd5c77
commit
01666ed068
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 -> {}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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}, ")
|
||||
}
|
||||
}
|
||||
}
|
@ -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}, ")
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ class DeterministicVerifierFactoryService(
|
||||
|
||||
baseSandboxConfiguration = SandboxConfiguration.createFor(
|
||||
analysisConfiguration = baseAnalysisConfiguration,
|
||||
profile = NODE_PROFILE,
|
||||
enableTracing = true
|
||||
profile = NODE_PROFILE
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
))
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)"
|
||||
}
|
@ -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())
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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))
|
@ -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!")
|
||||
}
|
@ -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)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user