From f68c4b3308c719aee77ad8f95bc58e999750e13e Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Tue, 3 Sep 2019 11:35:48 +0100 Subject: [PATCH] CORDA-3174: Extend serialisation to include InputStream and OpaqueBytesSubSequence. (#60) * Update DJVM Example project for serialisation. * Add serializers for InputStream and OpaqueBytesSubSequence. * Support ZIP Inflater and CRC32 inside the sandbox. * Allow the DJVM to wrap java.io.InputStream as sandbox.java.io.InputStream. * Configure tests also to preserve @DeprecatedConstructorForDeserialization. --- build.gradle | 4 +- .../deserializers/InputStreamDeserializer.kt | 11 ++++ .../OpaqueBytesSubSequenceDeserializer.kt | 11 ++++ .../serialization/AMQPSerializationScheme.kt | 2 + .../corda/djvm/serialization/Serialization.kt | 5 +- .../SandboxInputStreamSerializer.kt | 40 +++++++++++++++ ...SandboxOpaqueBytesSubSequenceSerializer.kt | 31 ++++++++++++ .../DeserializeInputStreamTest.kt | 45 +++++++++++++++++ .../DeserializeOpaqueBytesSubSequenceTest.kt | 50 +++++++++++++++++++ .../net/corda/djvm/serialization/TestBase.kt | 4 +- 10 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/InputStreamDeserializer.kt create mode 100644 deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/OpaqueBytesSubSequenceDeserializer.kt create mode 100644 src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxInputStreamSerializer.kt create mode 100644 src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt create mode 100644 src/test/kotlin/net/corda/djvm/serialization/DeserializeInputStreamTest.kt create mode 100644 src/test/kotlin/net/corda/djvm/serialization/DeserializeOpaqueBytesSubSequenceTest.kt diff --git a/build.gradle b/build.gradle index 30395c53f3..6fbcc81d9c 100644 --- a/build.gradle +++ b/build.gradle @@ -33,8 +33,8 @@ allprojects { cacheChangingModulesFor 0, 'seconds' dependencySubstitution { - substitute module("net.corda:corda-core:$corda_version") with module("net.corda:corda-core-deterministic:$corda_version") - substitute module("net.corda:corda-serialization:$corda_version") with module("net.corda:corda-serialization-deterministic:$corda_version") + substitute module("net.corda:corda-core") with module("net.corda:corda-core-deterministic:$corda_version") + substitute module("net.corda:corda-serialization") with module("net.corda:corda-serialization-deterministic:$corda_version") } } } diff --git a/deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/InputStreamDeserializer.kt b/deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/InputStreamDeserializer.kt new file mode 100644 index 0000000000..98938615ec --- /dev/null +++ b/deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/InputStreamDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.djvm.serialization.deserializers + +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.util.function.Function + +class InputStreamDeserializer : Function { + override fun apply(bytes: ByteArray): InputStream? { + return ByteArrayInputStream(bytes) + } +} diff --git a/deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/OpaqueBytesSubSequenceDeserializer.kt b/deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/OpaqueBytesSubSequenceDeserializer.kt new file mode 100644 index 0000000000..21423b9720 --- /dev/null +++ b/deserializers/src/main/kotlin/net/corda/djvm/serialization/deserializers/OpaqueBytesSubSequenceDeserializer.kt @@ -0,0 +1,11 @@ +package net.corda.djvm.serialization.deserializers + +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.OpaqueBytesSubSequence +import java.util.function.Function + +class OpaqueBytesSubSequenceDeserializer : Function { + override fun apply(proxy: OpaqueBytes): OpaqueBytesSubSequence { + return OpaqueBytesSubSequence(proxy.bytes, proxy.offset, proxy.size) + } +} diff --git a/src/main/kotlin/net/corda/djvm/serialization/AMQPSerializationScheme.kt b/src/main/kotlin/net/corda/djvm/serialization/AMQPSerializationScheme.kt index 90981d486a..348ae98f24 100644 --- a/src/main/kotlin/net/corda/djvm/serialization/AMQPSerializationScheme.kt +++ b/src/main/kotlin/net/corda/djvm/serialization/AMQPSerializationScheme.kt @@ -28,6 +28,7 @@ class AMQPSerializationScheme( register(SandboxCertPathSerializer(classLoader, executor, this)) register(SandboxDurationSerializer(classLoader, executor, this)) register(SandboxEnumSetSerializer(classLoader, executor, this)) + register(SandboxInputStreamSerializer(classLoader, executor)) register(SandboxInstantSerializer(classLoader, executor, this)) register(SandboxLocalDateSerializer(classLoader, executor, this)) register(SandboxLocalDateTimeSerializer(classLoader, executor, this)) @@ -40,6 +41,7 @@ class AMQPSerializationScheme( register(SandboxYearSerializer(classLoader, executor, this)) register(SandboxZonedDateTimeSerializer(classLoader, executor, this)) register(SandboxZoneIdSerializer(classLoader, executor, this)) + register(SandboxOpaqueBytesSubSequenceSerializer(classLoader, executor, this)) register(SandboxOptionalSerializer(classLoader, executor, this)) register(SandboxPrimitiveSerializer(UUID::class.java, classLoader, sandboxBasicInput)) register(SandboxPrimitiveSerializer(String::class.java, classLoader, sandboxBasicInput)) diff --git a/src/main/kotlin/net/corda/djvm/serialization/Serialization.kt b/src/main/kotlin/net/corda/djvm/serialization/Serialization.kt index 8e1f16d4a4..f774fe9bdd 100644 --- a/src/main/kotlin/net/corda/djvm/serialization/Serialization.kt +++ b/src/main/kotlin/net/corda/djvm/serialization/Serialization.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.SerializationContext.UseCase import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.internal.SerializationEnvironment +import net.corda.core.utilities.ByteSequence import net.corda.djvm.execution.SandboxRuntimeException import net.corda.djvm.rewiring.SandboxClassLoader import net.corda.djvm.serialization.serializers.PrimitiveSerializer @@ -73,11 +74,11 @@ inline fun SerializedBytes.deserializeFor(classLoader: Sand return deserializeTo(clazz, classLoader) } -fun SerializedBytes<*>.deserializeTo(clazz: Class<*>, classLoader: SandboxClassLoader): Any { +fun ByteSequence.deserializeTo(clazz: Class<*>, classLoader: SandboxClassLoader): Any { return deserializeTo(clazz, classLoader, SerializationFactory.defaultFactory) } -fun SerializedBytes<*>.deserializeTo(clazz: Class<*>, classLoader: SandboxClassLoader, factory: SerializationFactory): Any { +fun ByteSequence.deserializeTo(clazz: Class<*>, classLoader: SandboxClassLoader, factory: SerializationFactory): Any { val obj = factory.deserialize(this, Any::class.java, factory.defaultContext) return if (clazz.isInstance(obj)) { obj diff --git a/src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxInputStreamSerializer.kt b/src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxInputStreamSerializer.kt new file mode 100644 index 0000000000..c7b6465072 --- /dev/null +++ b/src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxInputStreamSerializer.kt @@ -0,0 +1,40 @@ +package net.corda.djvm.serialization.serializers + +import net.corda.core.serialization.SerializationContext +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.djvm.serialization.deserializers.InputStreamDeserializer +import net.corda.djvm.serialization.loadClassForSandbox +import net.corda.serialization.internal.amqp.* +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.BiFunction +import java.util.function.Function + +class SandboxInputStreamSerializer( + classLoader: SandboxClassLoader, + executor: BiFunction +) : CustomSerializer.Implements(classLoader.loadClassForSandbox(InputStream::class.java)) { + private val decoder: Function + + init { + val decodeTask = classLoader.loadClassForSandbox(InputStreamDeserializer::class.java).newInstance() + decoder = Function { inputs -> + executor.apply(decodeTask, inputs) + } + } + + override val schemaForDocumentation: Schema = Schema(emptyList()) + + override val deserializationAliases: Set> = singleton(InputStream::class.java) + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any { + val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray + return decoder.apply(bits)!! + } + + override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) { + abortReadOnly() + } +} diff --git a/src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt b/src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt new file mode 100644 index 0000000000..7d41b81930 --- /dev/null +++ b/src/main/kotlin/net/corda/djvm/serialization/serializers/SandboxOpaqueBytesSubSequenceSerializer.kt @@ -0,0 +1,31 @@ +package net.corda.djvm.serialization.serializers + +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.OpaqueBytesSubSequence +import net.corda.djvm.rewiring.SandboxClassLoader +import net.corda.djvm.serialization.deserializers.OpaqueBytesSubSequenceDeserializer +import net.corda.djvm.serialization.loadClassForSandbox +import net.corda.serialization.internal.amqp.CustomSerializer +import net.corda.serialization.internal.amqp.SerializerFactory +import java.util.Collections.singleton +import java.util.function.BiFunction + +class SandboxOpaqueBytesSubSequenceSerializer( + classLoader: SandboxClassLoader, + private val executor: BiFunction, + factory: SerializerFactory +) : CustomSerializer.Proxy( + clazz = classLoader.loadClassForSandbox(OpaqueBytesSubSequence::class.java), + proxyClass = classLoader.loadClassForSandbox(OpaqueBytes::class.java), + factory = factory +) { + private val task = classLoader.loadClassForSandbox(OpaqueBytesSubSequenceDeserializer::class.java).newInstance() + + override val deserializationAliases: Set> = singleton(OpaqueBytesSubSequence::class.java) + + override fun toProxy(obj: Any): Any = abortReadOnly() + + override fun fromProxy(proxy: Any): Any { + return executor.apply(task, proxy)!! + } +} diff --git a/src/test/kotlin/net/corda/djvm/serialization/DeserializeInputStreamTest.kt b/src/test/kotlin/net/corda/djvm/serialization/DeserializeInputStreamTest.kt new file mode 100644 index 0000000000..84cdd80712 --- /dev/null +++ b/src/test/kotlin/net/corda/djvm/serialization/DeserializeInputStreamTest.kt @@ -0,0 +1,45 @@ +package net.corda.djvm.serialization + +import net.corda.core.internal.readFully +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.djvm.serialization.SandboxType.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.io.InputStream +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeInputStreamTest : TestBase(KOTLIN) { + companion object { + const val MESSAGE = "Round and round the rugged rocks..." + } + + @Test + fun `test deserializing input stream`() { + val data = MESSAGE.byteInputStream().serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxStream = data.deserializeFor(classLoader) + + val executor = createExecutorFor(classLoader) + val result = executor.apply( + classLoader.loadClassForSandbox(ShowInputStream::class.java).newInstance(), + sandboxStream + ) ?: fail("Result cannot be null") + + assertEquals(String(MESSAGE.byteInputStream().readFully()), result.toString()) + assertEquals(SANDBOX_STRING, result::class.java.name) + } + } + + class ShowInputStream : Function { + override fun apply(input: InputStream): String { + return String(input.readFully()) + } + } +} diff --git a/src/test/kotlin/net/corda/djvm/serialization/DeserializeOpaqueBytesSubSequenceTest.kt b/src/test/kotlin/net/corda/djvm/serialization/DeserializeOpaqueBytesSubSequenceTest.kt new file mode 100644 index 0000000000..519a86e6e3 --- /dev/null +++ b/src/test/kotlin/net/corda/djvm/serialization/DeserializeOpaqueBytesSubSequenceTest.kt @@ -0,0 +1,50 @@ +package net.corda.djvm.serialization + +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytesSubSequence +import net.corda.djvm.serialization.SandboxType.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.fail +import java.util.function.Function + +@ExtendWith(LocalSerialization::class) +class DeserializeOpaqueBytesSubSequenceTest : TestBase(KOTLIN) { + companion object { + const val MESSAGE = "The rain in spain falls mainly on the plain." + const val OFFSET = MESSAGE.length / 2 + } + + @Test + fun `test deserializing opaquebytes subsequence`() { + val subSequence = OpaqueBytesSubSequence( + bytes = MESSAGE.toByteArray(), + offset = OFFSET, + size = MESSAGE.length - OFFSET + ) + val data = subSequence.serialize() + + sandbox { + _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) + + val sandboxBytes = data.deserializeFor(classLoader) + + val executor = createExecutorFor(classLoader) + val result = executor.apply( + classLoader.loadClassForSandbox(ShowOpaqueBytesSubSequence::class.java).newInstance(), + sandboxBytes + ) ?: fail("Result cannot be null") + + assertEquals(MESSAGE.substring(OFFSET), String(result as ByteArray)) + assertEquals(String(subSequence.copyBytes()), String(result)) + } + } + + class ShowOpaqueBytesSubSequence : Function { + override fun apply(sequence: OpaqueBytesSubSequence): ByteArray { + return sequence.copyBytes() + } + } +} diff --git a/src/test/kotlin/net/corda/djvm/serialization/TestBase.kt b/src/test/kotlin/net/corda/djvm/serialization/TestBase.kt index 0a178a5575..29af995f0f 100644 --- a/src/test/kotlin/net/corda/djvm/serialization/TestBase.kt +++ b/src/test/kotlin/net/corda/djvm/serialization/TestBase.kt @@ -2,6 +2,7 @@ package net.corda.djvm.serialization import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.djvm.SandboxConfiguration import net.corda.djvm.SandboxConfiguration.Companion.ALL_DEFINITION_PROVIDERS import net.corda.djvm.SandboxConfiguration.Companion.ALL_EMITTERS @@ -52,7 +53,8 @@ abstract class TestBase(type: SandboxType) { whitelist = MINIMAL, visibleAnnotations = setOf( CordaSerializable::class.java, - ConstructorForDeserialization::class.java + ConstructorForDeserialization::class.java, + DeprecatedConstructorForDeserialization::class.java ), bootstrapSource = BootstrapClassLoader(DETERMINISTIC_RT) )