From c61b036844abeff9a6d782a727e03787926c6b72 Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Fri, 25 Aug 2017 15:09:43 +0100 Subject: [PATCH] Some custom serializers, fixes to generics handling, some annotations and added NonEmptySet as a special case of Set. (#1330) --- .../net/corda/core/crypto/DigitalSignature.kt | 4 +- .../net/corda/core/messaging/Messaging.kt | 3 ++ .../core/utilities/NetworkHostAndPort.kt | 2 + .../serialization/AMQPSerializationScheme.kt | 2 + .../amqp/CollectionSerializer.kt | 4 +- .../internal/serialization/amqp/Schema.kt | 2 +- .../serialization/amqp/SerializationHelper.kt | 7 ++- .../custom/PartyAndCertificateSerializer.kt | 30 +++++++++++++ .../custom/X509CertificateHolderSerializer.kt | 23 ++++++++++ .../amqp/SerializationOutputTests.kt | 44 +++++++++++++++++++ 10 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt index db7cf6473a..0e0f36c606 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/DigitalSignature.kt @@ -11,9 +11,9 @@ import java.security.SignatureException // should be renamed to match. /** A wrapper around a digital signature. */ @CordaSerializable -open class DigitalSignature(bits: ByteArray) : OpaqueBytes(bits) { +open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) { /** A digital signature that identifies who the public key is owned by. */ - open class WithKey(val by: PublicKey, bits: ByteArray) : DigitalSignature(bits) { + open class WithKey(val by: PublicKey, bytes: ByteArray) : DigitalSignature(bytes) { /** * Utility to simplify the act of verifying a signature. * diff --git a/core/src/main/kotlin/net/corda/core/messaging/Messaging.kt b/core/src/main/kotlin/net/corda/core/messaging/Messaging.kt index 44a297e979..6a18a7702b 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/Messaging.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/Messaging.kt @@ -1,7 +1,10 @@ package net.corda.core.messaging +import net.corda.core.serialization.CordaSerializable + /** The interface for a group of message recipients (which may contain only one recipient) */ +@CordaSerializable interface MessageRecipients /** A base class for the case of point-to-point messages */ diff --git a/core/src/main/kotlin/net/corda/core/utilities/NetworkHostAndPort.kt b/core/src/main/kotlin/net/corda/core/utilities/NetworkHostAndPort.kt index 28d0e5c7dc..3472682639 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/NetworkHostAndPort.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/NetworkHostAndPort.kt @@ -1,5 +1,6 @@ package net.corda.core.utilities +import net.corda.core.serialization.CordaSerializable import java.net.URI /** @@ -7,6 +8,7 @@ import java.net.URI * @param host a hostname or IP address. IPv6 addresses must not be enclosed in square brackets. * @param port a valid port number. */ +@CordaSerializable data class NetworkHostAndPort(val host: String, val port: Int) { companion object { internal val invalidPortFormat = "Invalid port: %s" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt index dc489c458b..d4a38efe6a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt @@ -37,6 +37,8 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { 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.ClassSerializer(this)) + register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt index 2b8660ed45..5d218a1a2a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.utilities.NonEmptySet import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException import java.lang.reflect.ParameterizedType @@ -22,7 +23,8 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali List::class.java to { list -> Collections.unmodifiableList(list) }, Set::class.java to { list -> Collections.unmodifiableSet(LinkedHashSet(list)) }, SortedSet::class.java to { list -> Collections.unmodifiableSortedSet(TreeSet(list)) }, - NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) } + NavigableSet::class.java to { list -> Collections.unmodifiableNavigableSet(TreeSet(list)) }, + NonEmptySet::class.java to { list -> NonEmptySet.copyOf(list) } ) private fun findConcreteType(clazz: Class<*>): (List<*>) -> Collection<*> { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 11b4d30568..e82df6443e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -373,7 +373,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta // to the CorDapp but maybe reference to the JAR in the short term. hasher.putUnencodedChars(type.name) } else { - fingerprintForObject(type, contextType, alreadySeen, hasher, factory) + fingerprintForObject(type, type, alreadySeen, hasher, factory) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 40fc0ae240..f1a356a01d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -81,7 +81,7 @@ private fun propertiesForSerializationFromConstructor(kotlinConstructo val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz." + " If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler.") val returnType = resolveTypeVariables(getter.genericReturnType, type) - if (constructorParamTakesReturnTypeOfGetter(getter, param)) { + if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) { rc += PropertySerializer.make(name, getter, returnType, factory) } else { throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}") @@ -90,7 +90,10 @@ private fun propertiesForSerializationFromConstructor(kotlinConstructo return rc } -private fun constructorParamTakesReturnTypeOfGetter(getter: Method, param: KParameter): Boolean = TypeToken.of(param.type.javaType).isSupertypeOf(getter.genericReturnType) +private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean { + val typeToken = TypeToken.of(param.type.javaType) + return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType) +} private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): Collection { // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt new file mode 100644 index 0000000000..8bdde643d7 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt @@ -0,0 +1,30 @@ +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::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) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt new file mode 100644 index 0000000000..13ca64dd75 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt @@ -0,0 +1,23 @@ +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::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) + } +} \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 06cfb3c8d1..3e83fe6f62 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -15,11 +15,13 @@ import net.corda.nodeapi.internal.serialization.AbstractAMQPSerializationScheme import net.corda.nodeapi.internal.serialization.EmptyWhitelist import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.testing.BOB_IDENTITY import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_PUBKEY import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl +import org.junit.Ignore import org.junit.Test import java.io.IOException import java.io.NotSerializableException @@ -710,6 +712,48 @@ class SerializationOutputTests { serdes(obj, factory, factory2) } + // TODO: ignored due to Proton-J bug https://issues.apache.org/jira/browse/PROTON-1551 + @Ignore + @Test + fun `test certificate holder serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + + val obj = BOB_IDENTITY.certificate + serdes(obj, factory, factory2) + } + + // TODO: ignored due to Proton-J bug https://issues.apache.org/jira/browse/PROTON-1551 + @Ignore + @Test + fun `test party and certificate serialize`() { + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) + + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory2)) + + val obj = BOB_IDENTITY + serdes(obj, factory, factory2) + } + + class OtherGeneric + + open class GenericSuperclass(val param: OtherGeneric) + + class GenericSubclass(param: OtherGeneric) : GenericSuperclass(param) { + override fun equals(other: Any?): Boolean = other is GenericSubclass // This is a bit lame but we just want to check it doesn't throw exceptions + } + + @Test + fun `test generic in constructor serialize`() { + val obj = GenericSubclass(OtherGeneric()) + serdes(obj) + } + @Test fun `test StateRef serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())