Various cleanups to AMQP and Kryo serialisation:

* PartyAndCertificate serialiser has been converted to deal with just CertPath
* Removed X500Name serialiser as we no longer use the BC implementation and have our own CordaX500Name
* Converted X509 cert serialiser to use Java certs and not BC X509 cert holder
* Added to test to AMQP serialisation to make sure c'tor is invoked on deserialisation
This commit is contained in:
Shams Asari 2017-11-20 11:57:58 +00:00
parent 288eb5fcc4
commit f7e51a9ae1
12 changed files with 166 additions and 169 deletions

View File

@ -0,0 +1,13 @@
package net.corda.core.contracts
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test
class PrivacySaltTest {
@Test
fun `all-zero PrivacySalt not allowed`() {
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
PrivacySalt(ByteArray(32))
}.withMessage("Privacy salt should not be all zeros.")
}
}

View File

@ -36,7 +36,6 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
register(publicKeySerializer) register(publicKeySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.X500NameSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this))
@ -54,8 +53,8 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer)

View File

@ -0,0 +1,27 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.io.NotSerializableException
import java.security.cert.CertPath
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
class CertPathSerializer(factory: SerializerFactory)
: CustomSerializer.Proxy<CertPath, CertPathSerializer.CertPathProxy>(CertPath::class.java, CertPathProxy::class.java, factory) {
override fun toProxy(obj: CertPath): CertPathProxy = CertPathProxy(obj.type, obj.encoded)
override fun fromProxy(proxy: CertPathProxy): CertPath {
try {
val cf = CertificateFactory.getInstance(proxy.type)
return cf.generateCertPath(proxy.encoded.inputStream())
} catch (ce: CertificateException) {
val nse = NotSerializableException("java.security.cert.CertPath: $type")
nse.initCause(ce)
throw nse
}
}
@Suppress("ArrayInDataClass")
data class CertPathProxy(val type: String, val encoded: ByteArray)
}

View File

@ -1,30 +0,0 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.core.identity.PartyAndCertificate
import net.corda.nodeapi.internal.serialization.amqp.*
import java.io.ByteArrayInputStream
import java.io.NotSerializableException
import java.security.cert.CertPath
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
/**
* A serializer that writes out a party and certificate in encoded format.
*/
class PartyAndCertificateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<PartyAndCertificate, PartyAndCertificateSerializer.PartyAndCertificateProxy>(PartyAndCertificate::class.java, PartyAndCertificateProxy::class.java, factory) {
override fun toProxy(obj: PartyAndCertificate): PartyAndCertificateProxy = PartyAndCertificateProxy(obj.certPath.type, obj.certPath.encoded)
override fun fromProxy(proxy: PartyAndCertificateProxy): PartyAndCertificate {
try {
val cf = CertificateFactory.getInstance(proxy.type)
return PartyAndCertificate(cf.generateCertPath(ByteArrayInputStream(proxy.encoded)))
} catch (ce: CertificateException) {
val nse = NotSerializableException("java.security.cert.CertPath: " + type)
nse.initCause(ce)
throw nse
}
}
data class PartyAndCertificateProxy(val type: String, val encoded: ByteArray)
}

View File

@ -1,23 +0,0 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.x500.X500Name
import java.lang.reflect.Type
/**
* Custom serializer for X500 names that utilizes their ASN.1 encoding on the wire.
*/
object X500NameSerializer : CustomSerializer.Implements<X500Name>(X500Name::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: X500Name, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X500Name {
val binary = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return X500Name.getInstance(ASN1InputStream(binary).readObject())
}
}

View File

@ -1,23 +0,0 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import org.bouncycastle.cert.X509CertificateHolder
import java.lang.reflect.Type
/**
* A serializer that writes out a certificate in X.509 format.
*/
object X509CertificateHolderSerializer : CustomSerializer.Implements<X509CertificateHolder>(X509CertificateHolder::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: X509CertificateHolder, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509CertificateHolder {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return X509CertificateHolder(bits)
}
}

View File

@ -0,0 +1,27 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object X509CertificateSerializer : CustomSerializer.Implements<X509Certificate>(X509Certificate::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(
type.toString(),
"",
listOf(type.toString()),
SerializerFactory.primitiveTypeName(ByteArray::class.java)!!,
descriptor,
emptyList()
)))
override fun writeDescribedObject(obj: X509Certificate, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509Certificate {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return CertificateFactory.getInstance("X.509").generateCertificate(bits.inputStream()) as X509Certificate
}
}

View File

@ -32,8 +32,6 @@ import net.corda.nodeapi.internal.serialization.GeneratedAttachment
import net.corda.nodeapi.internal.serialization.MutableClassWhitelist import net.corda.nodeapi.internal.serialization.MutableClassWhitelist
import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
@ -53,6 +51,7 @@ import java.io.InputStream
import java.lang.reflect.Modifier.isPublic import java.lang.reflect.Modifier.isPublic
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -109,8 +108,7 @@ object DefaultKryoCustomizer {
register(FileInputStream::class.java, InputStreamSerializer) register(FileInputStream::class.java, InputStreamSerializer)
register(CertPath::class.java, CertPathSerializer) register(CertPath::class.java, CertPathSerializer)
register(X509CertPath::class.java, CertPathSerializer) register(X509CertPath::class.java, CertPathSerializer)
register(X500Name::class.java, X500NameSerializer) register(X509Certificate::class.java, X509CertificateSerializer)
register(X509CertificateHolder::class.java, X509CertificateSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, publicKeySerializer) register(BCECPublicKey::class.java, publicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)

View File

@ -15,7 +15,8 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.* import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint
import net.corda.core.serialization.SerializationContext.UseCase.Storage
import net.corda.core.serialization.SerializeAsTokenContext import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.toFuture import net.corda.core.toFuture
@ -23,9 +24,6 @@ import net.corda.core.toObservable
import net.corda.core.transactions.* import net.corda.core.transactions.*
import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.CordaClassResolver
import net.corda.nodeapi.internal.serialization.serializationContextKey import net.corda.nodeapi.internal.serialization.serializationContextKey
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rx.Observable import rx.Observable
@ -36,6 +34,7 @@ import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.* import java.util.*
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -471,46 +470,28 @@ object ClassSerializer : Serializer<Class<*>>() {
} }
} }
/**
* For serialising an [X500Name] without touching Sun internal classes.
*/
@ThreadSafe
object X500NameSerializer : Serializer<X500Name>() {
override fun read(kryo: Kryo, input: Input, type: Class<X500Name>): X500Name {
return X500Name.getInstance(ASN1InputStream(input.readBytes()).readObject())
}
override fun write(kryo: Kryo, output: Output, obj: X500Name) {
output.writeBytes(obj.encoded)
}
}
/**
* For serialising an [CertPath] in an X.500 standard format.
*/
@ThreadSafe @ThreadSafe
object CertPathSerializer : Serializer<CertPath>() { object CertPathSerializer : Serializer<CertPath>() {
val factory: CertificateFactory = CertificateFactory.getInstance("X.509")
override fun read(kryo: Kryo, input: Input, type: Class<CertPath>): CertPath { override fun read(kryo: Kryo, input: Input, type: Class<CertPath>): CertPath {
return factory.generateCertPath(input) val factory = CertificateFactory.getInstance(input.readString())
return factory.generateCertPath(input.readBytesWithLength().inputStream())
} }
override fun write(kryo: Kryo, output: Output, obj: CertPath) { override fun write(kryo: Kryo, output: Output, obj: CertPath) {
output.writeBytes(obj.encoded) output.writeString(obj.type)
output.writeBytesWithLength(obj.encoded)
} }
} }
/**
* For serialising an [X509CertificateHolder] in an X.500 standard format.
*/
@ThreadSafe @ThreadSafe
object X509CertificateSerializer : Serializer<X509CertificateHolder>() { object X509CertificateSerializer : Serializer<X509Certificate>() {
override fun read(kryo: Kryo, input: Input, type: Class<X509CertificateHolder>): X509CertificateHolder { override fun read(kryo: Kryo, input: Input, type: Class<X509Certificate>): X509Certificate {
return X509CertificateHolder(input.readBytes()) val factory = CertificateFactory.getInstance("X.509")
return factory.generateCertificate(input.readBytesWithLength().inputStream()) as X509Certificate
} }
override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) { override fun write(kryo: Kryo, output: Output, obj: X509Certificate) {
output.writeBytes(obj.encoded) output.writeBytesWithLength(obj.encoded)
} }
} }

View File

@ -21,13 +21,14 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.time.Instant import java.time.Instant
import java.util.Collections import java.util.*
import kotlin.test.* import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class KryoTests { class KryoTests {
@Rule @Rule
@ -36,9 +37,6 @@ class KryoTests {
private lateinit var factory: SerializationFactory private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext private lateinit var context: SerializationContext
@get:Rule
val expectedEx: ExpectedException = ExpectedException.none()
@Before @Before
fun setup() { fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
@ -51,7 +49,7 @@ class KryoTests {
} }
@Test @Test
fun ok() { fun `simple data class`() {
val birthday = Instant.parse("1984-04-17T00:30:00.00Z") val birthday = Instant.parse("1984-04-17T00:30:00.00Z")
val mike = Person("mike", birthday) val mike = Person("mike", birthday)
val bits = mike.serialize(factory, context) val bits = mike.serialize(factory, context)
@ -59,7 +57,7 @@ class KryoTests {
} }
@Test @Test
fun nullables() { fun `null values`() {
val bob = Person("bob", null) val bob = Person("bob", null)
val bits = bob.serialize(factory, context) val bits = bob.serialize(factory, context)
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null)) assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
@ -202,13 +200,6 @@ class KryoTests {
assertEquals(expected, actual) assertEquals(expected, actual)
} }
@Test
fun `all-zero PrivacySalt not allowed`() {
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("Privacy salt should not be all zeros.")
PrivacySalt(ByteArray(32))
}
@CordaSerializable @CordaSerializable
private object TestSingleton private object TestSingleton

View File

@ -2,16 +2,16 @@ package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData import net.corda.node.services.statemachine.SessionData
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.amqpSpecific import net.corda.testing.amqpSpecific
import net.corda.testing.kryoSpecific import net.corda.testing.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Assertions
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -53,7 +53,7 @@ class MapsSerializationTest {
fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") { fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") {
val payload = HashMap<String, String>(smallMap) val payload = HashMap<String, String>(smallMap)
val wrongPayloadType = WrongPayloadType(payload) val wrongPayloadType = WrongPayloadType(payload)
Assertions.assertThatThrownBy { wrongPayloadType.serialize() } assertThatThrownBy { wrongPayloadType.serialize() }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining( .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining(
"Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.") "Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
} }
@ -62,27 +62,29 @@ class MapsSerializationTest {
data class MyKey(val keyContent: Double) data class MyKey(val keyContent: Double)
@CordaSerializable @CordaSerializable
data class MyValue(val valueContent: X500Name) data class MyValue(val valueContent: CordaX500Name)
@Test @Test
fun `check map serialization works with custom types`() { fun `check map serialization works with custom types`() {
val myMap = mapOf( val myMap = mapOf(
MyKey(1.0) to MyValue(X500Name("CN=one")), MyKey(1.0) to MyValue(CordaX500Name("OOO", "LLL", "CC")),
MyKey(10.0) to MyValue(X500Name("CN=ten"))) MyKey(10.0) to MyValue(CordaX500Name("OO", "LL", "CC")))
assertEqualAfterRoundTripSerialization(myMap) assertEqualAfterRoundTripSerialization(myMap)
} }
@Test @Test
fun `check empty map serialises as Java emptyMap`() = kryoSpecific("Specifically checks Kryo serialization") { fun `check empty map serialises as Java emptyMap`() {
val nameID = 0 kryoSpecific("Specifically checks Kryo serialization") {
val serializedForm = emptyMap<Int, Int>().serialize() val nameID = 0
val output = ByteArrayOutputStream().apply { val serializedForm = emptyMap<Int, Int>().serialize()
write(KryoHeaderV0_1.bytes) val output = ByteArrayOutputStream().apply {
write(DefaultClassResolver.NAME + 2) write(KryoHeaderV0_1.bytes)
write(nameID) write(DefaultClassResolver.NAME + 2)
write(javaEmptyMapClass.name.toAscii()) write(nameID)
write(Kryo.NOT_NULL.toInt()) write(javaEmptyMapClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
} }
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
} }
} }

View File

@ -1,17 +1,20 @@
@file:Suppress("unused", "MemberVisibilityCanPrivate")
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.client.rpc.RPCException
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.internal.toX509CertHolder import net.corda.core.internal.AbstractAttachment
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.client.rpc.RPCException import net.corda.core.utilities.OpaqueBytes
import net.corda.core.contracts.*
import net.corda.core.internal.AbstractAttachment
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.EmptyWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.GeneratedAttachment
@ -25,10 +28,8 @@ import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl import org.apache.qpid.proton.codec.EncoderImpl
import org.junit.Assert.assertArrayEquals import org.assertj.core.api.Assertions.*
import org.junit.Assert.assertNotSame import org.junit.Assert.*
import org.junit.Assert.assertSame
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -39,10 +40,7 @@ import java.nio.ByteBuffer
import java.time.* import java.time.*
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.superclasses import kotlin.reflect.full.superclasses
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -79,7 +77,6 @@ class SerializationOutputTests {
} }
data class Woo(val fred: Int) { data class Woo(val fred: Int) {
@Suppress("unused")
val bob = "Bob" val bob = "Bob"
} }
@ -89,7 +86,6 @@ class SerializationOutputTests {
@CordaSerializable @CordaSerializable
data class AnnotatedWoo(val fred: Int) { data class AnnotatedWoo(val fred: Int) {
@Suppress("unused")
val bob = "Bob" val bob = "Bob"
} }
@ -151,6 +147,13 @@ class SerializationOutputTests {
data class PolymorphicProperty(val foo: FooInterface?) data class PolymorphicProperty(val foo: FooInterface?)
@CordaSerializable
class NonZeroByte(val value: Byte) {
init {
require(value.toInt() != 0) { "Zero not allowed" }
}
}
private inline fun <reified T : Any> serdes(obj: T, private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory( factory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()), AllWhitelist, ClassLoader.getSystemClassLoader()),
@ -406,6 +409,32 @@ class SerializationOutputTests {
serdes(obj) serdes(obj)
} }
@Test
fun `class constructor is invoked on deserialisation`() {
val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
val des = DeserializationInput(ser.serializerFactory)
val serialisedOne = ser.serialize(NonZeroByte(1)).bytes
val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes
// Find the index that holds the value byte
val valueIndex = serialisedOne.zip(serialisedTwo).mapIndexedNotNull { index, (oneByte, twoByte) ->
if (oneByte.toInt() == 1 && twoByte.toInt() == 2) index else null
}.single()
val copy = serialisedTwo.clone()
// Double check
copy[valueIndex] = 0x03
assertThat(des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java).value).isEqualTo(3)
// Now use the forbidden value
copy[valueIndex] = 0x00
assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy {
des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java)
}.withMessageContaining("Zero not allowed")
}
@Test @Test
fun `test custom serializers on public key`() { fun `test custom serializers on public key`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -762,26 +791,32 @@ class SerializationOutputTests {
} }
@Test @Test
fun `test certificate holder serialize`() { fun `test privacy salt serialize`() {
serdes(PrivacySalt())
serdes(PrivacySalt(secureRandomBytes(32)))
}
@Test
fun `test X509 certificate serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
val obj = BOB_IDENTITY.certificate.toX509CertHolder() val obj = BOB_IDENTITY.certificate
serdes(obj, factory, factory2) serdes(obj, factory, factory2)
} }
@Test @Test
fun `test party and certificate serialize`() { fun `test cert path serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory2)) factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory2))
val obj = BOB_IDENTITY val obj = BOB_IDENTITY.certPath
serdes(obj, factory, factory2) serdes(obj, factory, factory2)
} }