Merge from Corda master

This commit is contained in:
szymonsztuka
2017-10-16 18:03:07 +01:00
706 changed files with 20091 additions and 11187 deletions

View File

@ -68,7 +68,7 @@ object RPCApi {
val RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION =
"${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_REMOVED.name}' AND " +
"${ManagementHelper.HDR_ROUTING_NAME} LIKE '$RPC_CLIENT_QUEUE_NAME_PREFIX.%'"
"${ManagementHelper.HDR_ROUTING_NAME} LIKE '$RPC_CLIENT_QUEUE_NAME_PREFIX.%'"
val RPC_CLIENT_BINDING_ADDITION_FILTER_EXPRESSION =
"${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_ADDED.name}' AND " +
"${ManagementHelper.HDR_ROUTING_NAME} LIKE '$RPC_CLIENT_QUEUE_NAME_PREFIX.%'"
@ -97,20 +97,20 @@ object RPCApi {
* @param clientAddress return address to contact the client at.
* @param id a unique ID for the request, which the server will use to identify its response with.
* @param methodName name of the method (procedure) to be called.
* @param arguments arguments to pass to the method, if any.
* @param serialisedArguments Serialised arguments to pass to the method, if any.
*/
data class RpcRequest(
val clientAddress: SimpleString,
val id: RpcRequestId,
val methodName: String,
val arguments: List<Any?>
val serialisedArguments: ByteArray
) : ClientToServer() {
fun writeToClientMessage(context: SerializationContext, message: ClientMessage) {
fun writeToClientMessage(message: ClientMessage) {
MessageUtil.setJMSReplyTo(message, clientAddress)
message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REQUEST.ordinal)
message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong)
message.putStringProperty(METHOD_NAME_FIELD_NAME, methodName)
message.bodyBuffer.writeBytes(arguments.serialize(context = context).bytes)
message.bodyBuffer.writeBytes(serialisedArguments)
}
}
@ -128,20 +128,20 @@ object RPCApi {
}
companion object {
fun fromClientMessage(context: SerializationContext, message: ClientMessage): ClientToServer {
fun fromClientMessage(message: ClientMessage): ClientToServer {
val tag = Tag.values()[message.getIntProperty(TAG_FIELD_NAME)]
return when (tag) {
RPCApi.ClientToServer.Tag.RPC_REQUEST -> RpcRequest(
clientAddress = MessageUtil.getJMSReplyTo(message),
id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME)),
methodName = message.getStringProperty(METHOD_NAME_FIELD_NAME),
arguments = message.getBodyAsByteArray().deserialize(context = context)
serialisedArguments = message.getBodyAsByteArray()
)
RPCApi.ClientToServer.Tag.OBSERVABLES_CLOSED -> {
val ids = ArrayList<ObservableId>()
val buffer = message.bodyBuffer
val numberOfIds = buffer.readInt()
for (i in 1 .. numberOfIds) {
for (i in 1..numberOfIds) {
ids.add(ObservableId(buffer.readLong()))
}
ObservablesClosed(ids)

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi
import net.corda.nodeapi.config.OldConfig
import net.corda.nodeapi.config.toConfig
data class User(
@OldConfig("user")
@ -8,9 +9,6 @@ data class User(
val password: String,
val permissions: Set<String>) {
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
fun toMap() = mapOf(
"username" to username,
"password" to password,
"permissions" to permissions
)
@Deprecated("Use toConfig().root().unwrapped() instead")
fun toMap(): Map<String, Any> = toConfig().root().unwrapped()
}

View File

@ -1,18 +1,26 @@
@file:JvmName("ConfigUtilities")
package net.corda.nodeapi.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigUtil
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NetworkHostAndPort
import org.slf4j.LoggerFactory
import java.lang.reflect.Field
import java.lang.reflect.Modifier.isStatic
import java.lang.reflect.ParameterizedType
import java.net.Proxy
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Instant
import java.time.LocalDate
import java.time.temporal.Temporal
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
@ -25,7 +33,7 @@ import kotlin.reflect.jvm.jvmErasure
annotation class OldConfig(val value: String)
// TODO Move other config parsing to use parseAs and remove this
operator fun <T> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
return getValueInternal(metadata.name, metadata.returnType)
}
@ -52,9 +60,8 @@ fun Config.toProperties(): Properties {
{ it.value.unwrapped().toString() })
}
@Suppress("UNCHECKED_CAST")
private fun <T> Config.getValueInternal(path: String, type: KType): T {
return (if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type)) as T
private fun <T : Any> Config.getValueInternal(path: String, type: KType): T {
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type))
}
private fun Config.getSingleValue(path: String, type: KType): Any? {
@ -71,8 +78,8 @@ private fun Config.getSingleValue(path: String, type: KType): Any? {
NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
Path::class -> Paths.get(getString(path))
URL::class -> URL(getString(path))
Properties::class -> getConfig(path).toProperties()
CordaX500Name::class -> CordaX500Name.parse(getString(path))
Properties::class -> getConfig(path).toProperties()
else -> if (typeClass.java.isEnum) {
parseEnum(typeClass.java, getString(path))
} else {
@ -122,9 +129,69 @@ private fun Config.defaultToOldPath(property: KProperty<*>): String {
return property.name
}
@Suppress("UNCHECKED_CAST")
private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge(enumType as Class<Proxy.Type>, name) // Any enum will do
private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge<Proxy.Type>(uncheckedCast(enumType), name) // Any enum will do
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T = java.lang.Enum.valueOf(clazz, name)
/**
* Convert the receiver object into a [Config]. This does the inverse action of [parseAs].
*/
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
private fun Any.toConfigMap(): Map<String, Any> {
val values = HashMap<String, Any>()
for (field in javaClass.declaredFields) {
if (isStatic(field.modifiers) || field.isSynthetic) continue
field.isAccessible = true
val value = field.get(this) ?: continue
val configValue = if (value is String || value is Boolean || value is Number) {
// These types are supported by Config as use as is
value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL) {
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
value.toString()
} else if (value is Enum<*>) {
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
value.name
} else if (value is Properties) {
// For Properties we treat keys with . as nested configs
ConfigFactory.parseMap(uncheckedCast(value)).root()
} else if (value is Iterable<*>) {
value.toConfigIterable(field)
} else {
// Else this is a custom object recursed over
value.toConfigMap()
}
values[field.name] = configValue
}
return values
}
// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
return when (elementType) {
// For the types already supported by Config we can use the Iterable as is
String::class.java -> this
Integer::class.java -> this
java.lang.Long::class.java -> this
java.lang.Double::class.java -> this
java.lang.Boolean::class.java -> this
LocalDate::class.java -> map(Any?::toString)
Instant::class.java -> map(Any?::toString)
NetworkHostAndPort::class.java -> map(Any?::toString)
Path::class.java -> map(Any?::toString)
URL::class.java -> map(Any?::toString)
CordaX500Name::class.java -> map(Any?::toString)
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
else -> if (elementType.isEnum) {
map { (it as Enum<*>).name }
} else {
map { it?.toConfigMap() }
}
}
}
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.config")

View File

@ -72,7 +72,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
val stream = ByteArrayOutputStream()
try {
attachment.extractFile(path, stream)
} catch(e: FileNotFoundException) {
} catch (e: FileNotFoundException) {
throw ClassNotFoundException(name)
}
val bytes = stream.toByteArray()
@ -99,7 +99,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
val stream = ByteArrayOutputStream()
attachment.extractFile(path, stream)
return ByteArrayInputStream(stream.toByteArray())
} catch(e: FileNotFoundException) {
} catch (e: FileNotFoundException) {
return null
}
}

View File

@ -27,7 +27,6 @@ class ServiceType private constructor(val id: String) {
}
val notary: ServiceType = corda.getSubType("notary")
val networkMap: ServiceType = corda.getSubType("network_map")
fun parse(id: String): ServiceType = ServiceType(id)

View File

@ -2,7 +2,6 @@
package net.corda.nodeapi.internal.serialization
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
@ -14,12 +13,6 @@ import java.util.concurrent.ConcurrentHashMap
val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == AmqpHeaderV1_0
class AMQPSerializationCustomization(val factory: SerializerFactory) : SerializationCustomization {
override fun addToWhitelist(vararg types: Class<*>) {
factory.addToWhitelist(*types)
}
}
fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
require(types.toSet().size == types.size) {
val duplicates = types.toMutableList()
@ -33,13 +26,14 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
abstract class AbstractAMQPSerializationScheme : SerializationScheme {
internal companion object {
private val pluginRegistries: List<CordaPluginRegistry> by lazy {
ServiceLoader.load(CordaPluginRegistry::class.java, this::class.java.classLoader).toList()
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist
}
fun registerCustomSerializers(factory: SerializerFactory) {
factory.apply {
with(factory) {
register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer)
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.X500NameSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer)
@ -67,8 +61,8 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
register(net.corda.nodeapi.internal.serialization.amqp.custom.BitSetSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this))
}
val customizer = AMQPSerializationCustomization(factory)
pluginRegistries.forEach { it.customizeSerialization(customizer) }
for (whitelistProvider in serializationWhitelists)
factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray())
}
}

View File

@ -30,6 +30,9 @@ import kotlin.collections.LinkedHashSet
* we can often end up pulling in a lot of objects that do not make sense to put in a checkpoint.
* Thus, by blacklisting classes/interfaces we don't expect to be serialised, we can better handle/monitor the aforementioned behaviour.
* Inheritance works for blacklisted items, but one can specifically exclude classes from blacklisting as well.
* Note: Custom serializer registration trumps white/black lists. So if a given type has a custom serializer and has its name
* in the blacklist - it will still be serialized as specified by custom serializer.
* For more details, see [net.corda.nodeapi.internal.serialization.CordaClassResolver.getRegistration]
*/
object AllButBlacklisted : ClassWhitelist {

View File

@ -1,4 +1,5 @@
@file:JvmName("ClientContexts")
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.SerializationContext

View File

@ -11,6 +11,7 @@ import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy
import java.io.PrintWriter
import java.lang.reflect.Modifier.isAbstract
import java.nio.charset.StandardCharsets
@ -30,9 +31,9 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl
* The point is that we do not want to send Kotlin types "over the wire" via RPC.
*/
private val javaAliases: Map<Class<*>, Class<*>> = mapOf(
listOf<Any>().javaClass to Collections.emptyList<Any>().javaClass,
setOf<Any>().javaClass to Collections.emptySet<Any>().javaClass,
mapOf<Any, Any>().javaClass to Collections.emptyMap<Any, Any>().javaClass
listOf<Any>().javaClass to Collections.emptyList<Any>().javaClass,
setOf<Any>().javaClass to Collections.emptySet<Any>().javaClass,
mapOf<Any, Any>().javaClass to Collections.emptyMap<Any, Any>().javaClass
)
private fun typeForSerializationOf(type: Class<*>): Class<*> = javaAliases[type] ?: type
@ -56,14 +57,13 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl
private fun checkClass(type: Class<*>): Registration? {
// If call path has disabled whitelisting (see [CordaKryo.register]), just return without checking.
if (!whitelistEnabled) return null
// Allow primitives, abstracts and interfaces
if (type.isPrimitive || type == Any::class.java || isAbstract(type.modifiers) || type == String::class.java) return null
// If array, recurse on element type
if (type.isArray) return checkClass(type.componentType)
// Specialised enum entry, so just resolve the parent Enum type since cannot annotate the specialised entry.
if (!type.isEnum && Enum::class.java.isAssignableFrom(type)) return checkClass(type.superclass)
// Kotlin lambdas require some special treatment
if (kotlin.jvm.internal.Lambda::class.java.isAssignableFrom(type)) return null
// Allow primitives, abstracts and interfaces. Note that we can also create abstract Enum types,
// but we don't want to whitelist those here.
if (type.isPrimitive || type == Any::class.java || type == String::class.java || (!type.isEnum && isAbstract(type.modifiers))) return null
// It's safe to have the Class already, since Kryo loads it with initialisation off.
// If we use a whitelist with blacklisting capabilities, whitelist.hasListed(type) may throw an IllegalStateException if input class is blacklisted.
// Thus, blacklisting precedes annotation checking.
@ -115,13 +115,7 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl
return (type.classLoader !is AttachmentsClassLoader)
&& !KryoSerializable::class.java.isAssignableFrom(type)
&& !type.isAnnotationPresent(DefaultSerializer::class.java)
&& (type.isAnnotationPresent(CordaSerializable::class.java) || hasInheritedAnnotation(type))
}
// Recursively check interfaces for our annotation.
private fun hasInheritedAnnotation(type: Class<*>): Boolean {
return type.interfaces.any { it.isAnnotationPresent(CordaSerializable::class.java) || hasInheritedAnnotation(it) }
|| (type.superclass != null && hasInheritedAnnotation(type.superclass))
&& (type.isAnnotationPresent(CordaSerializable::class.java) || whitelist.hasAnnotationInHierarchy(type))
}
// Need to clear out class names from attachments.
@ -157,14 +151,15 @@ object AllWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = true
}
// TODO: Need some concept of from which class loader
class GlobalTransientClassWhiteList(val delegate: ClassWhitelist) : MutableClassWhitelist, ClassWhitelist by delegate {
companion object {
val whitelist: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
}
sealed class AbstractMutableClassWhitelist(private val whitelist: MutableSet<String>, private val delegate: ClassWhitelist) : MutableClassWhitelist {
override fun hasListed(type: Class<*>): Boolean {
return (type.name in whitelist) || delegate.hasListed(type)
/**
* There are certain delegates like [net.corda.nodeapi.internal.serialization.AllButBlacklisted]
* which may throw when asked whether the type is listed.
* In such situations - it may be a good idea to ask [delegate] first before making a check against own [whitelist].
*/
return delegate.hasListed(type) || (type.name in whitelist)
}
override fun add(entry: Class<*>) {
@ -172,21 +167,17 @@ class GlobalTransientClassWhiteList(val delegate: ClassWhitelist) : MutableClass
}
}
// TODO: Need some concept of from which class loader
class GlobalTransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWhitelist(GlobalTransientClassWhiteList.whitelist, delegate) {
companion object {
private val whitelist: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
}
}
/**
* A whitelist that can be customised via the [net.corda.core.node.CordaPluginRegistry], since implements [MutableClassWhitelist].
* A whitelist that can be customised via the [net.corda.core.node.SerializationWhitelist], since it implements [MutableClassWhitelist].
*/
class TransientClassWhiteList(val delegate: ClassWhitelist) : MutableClassWhitelist, ClassWhitelist by delegate {
val whitelist: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
override fun hasListed(type: Class<*>): Boolean {
return (type.name in whitelist) || delegate.hasListed(type)
}
override fun add(entry: Class<*>) {
whitelist += entry.name
}
}
class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWhitelist(Collections.synchronizedSet(mutableSetOf()), delegate)
/**
* This class is not currently used, but can be installed to log a large number of missing entries from the whitelist
@ -204,7 +195,7 @@ class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true)
if (fileName != null && fileName.isNotEmpty()) {
try {
return PrintWriter(Files.newBufferedWriter(Paths.get(fileName), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.WRITE), true)
} catch(ioEx: Exception) {
} catch (ioEx: Exception) {
log.error("Could not open/create whitelist journal file for append: $fileName", ioEx)
}
}

View File

@ -15,7 +15,7 @@ import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes
import net.corda.core.transactions.NotaryChangeWireTransaction
@ -49,8 +49,8 @@ import java.util.*
import kotlin.collections.ArrayList
object DefaultKryoCustomizer {
private val pluginRegistries: List<CordaPluginRegistry> by lazy {
ServiceLoader.load(CordaPluginRegistry::class.java, this.javaClass.classLoader).toList()
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
ServiceLoader.load(SerializationWhitelist::class.java, this.javaClass.classLoader).toList() + DefaultWhitelist
}
fun customize(kryo: Kryo): Kryo {
@ -88,10 +88,10 @@ object DefaultKryoCustomizer {
register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
noReferencesWithin<WireTransaction>()
register(ECPublicKeyImpl::class.java, ECPublicKeyImplSerializer)
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
register(CompositeKey::class.java, CompositeKeySerializer) // Using a custom serializer for compactness
register(ECPublicKeyImpl::class.java, PublicKeySerializer)
register(EdDSAPublicKey::class.java, PublicKeySerializer)
register(EdDSAPrivateKey::class.java, PrivateKeySerializer)
register(CompositeKey::class.java, PublicKeySerializer) // Using a custom serializer for compactness
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
register(Array<StackTraceElement>::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> })
// This ensures a NonEmptySetSerializer is constructed with an initial value.
@ -109,7 +109,6 @@ object DefaultKryoCustomizer {
register(BCRSAPublicKey::class.java, PublicKeySerializer)
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
register(BCSphincs256PublicKey::class.java, PublicKeySerializer)
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer)
register(PartyAndCertificate::class.java, PartyAndCertificateSerializer)
@ -122,8 +121,17 @@ object DefaultKryoCustomizer {
register(java.lang.invoke.SerializedLambda::class.java)
register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
for (whitelistProvider in serializationWhitelists) {
val types = whitelistProvider.whitelist
require(types.toSet().size == types.size) {
val duplicates = types.toMutableList()
types.toSet().forEach { duplicates -= it }
"Cannot add duplicate classes to the whitelist ($duplicates)."
}
for (type in types) {
((kryo.classResolver as? CordaClassResolver)?.whitelist as? MutableClassWhitelist)?.add(type)
}
}
}
}
@ -132,6 +140,7 @@ object DefaultKryoCustomizer {
// Use this to allow construction of objects using a JVM backdoor that skips invoking the constructors, if there
// is no no-arg constructor available.
private val defaultStrategy = Kryo.DefaultInstantiatorStrategy(fallbackStrategy)
override fun <T> newInstantiatorOf(type: Class<T>): ObjectInstantiator<T> {
// However this doesn't work for non-public classes in the java. namespace
val strat = if (type.name.startsWith("java.") && !isPublic(type.modifiers)) fallbackStrategy else defaultStrategy
@ -143,6 +152,7 @@ object DefaultKryoCustomizer {
override fun write(kryo: Kryo, output: Output, obj: PartyAndCertificate) {
kryo.writeClassAndObject(output, obj.certPath)
}
override fun read(kryo: Kryo, input: Input, type: Class<PartyAndCertificate>): PartyAndCertificate {
return PartyAndCertificate(kryo.readClassAndObject(input) as CertPath)
}

View File

@ -1,8 +1,7 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.KryoException
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.serialization.SerializationCustomization
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.NetworkHostAndPort
import org.apache.activemq.artemis.api.core.SimpleString
import rx.Notification
@ -12,10 +11,9 @@ import java.util.*
/**
* NOTE: We do not whitelist [HashMap] or [HashSet] since they are unstable under serialization.
*/
class DefaultWhitelist : CordaPluginRegistry() {
override fun customizeSerialization(custom: SerializationCustomization): Boolean {
custom.apply {
addToWhitelist(Array<Any>(0, {}).javaClass,
object DefaultWhitelist : SerializationWhitelist {
override val whitelist =
listOf(Array<Any>(0, {}).javaClass,
Notification::class.java,
Notification.Kind::class.java,
ArrayList::class.java,
@ -60,8 +58,6 @@ class DefaultWhitelist : CordaPluginRegistry() {
java.util.LinkedHashMap::class.java,
BitSet::class.java,
OnErrorNotImplementedException::class.java,
StackTraceElement::class.java)
}
return true
}
StackTraceElement::class.java
)
}

View File

@ -10,11 +10,12 @@ import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.toFuture
@ -24,20 +25,12 @@ import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.SgxSupport
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import rx.Observable
import sun.security.ec.ECPublicKeyImpl
import sun.security.util.DerValue
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.lang.reflect.InvocationTargetException
@ -253,9 +246,8 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
kryo.writeClassAndObject(output, obj.privacySalt)
}
@Suppress("UNCHECKED_CAST")
override fun read(kryo: Kryo, input: Input, type: Class<WireTransaction>): WireTransaction {
val componentGroups = kryo.readClassAndObject(input) as List<ComponentGroup>
val componentGroups: List<ComponentGroup> = uncheckedCast(kryo.readClassAndObject(input))
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
return WireTransaction(componentGroups, privacySalt)
}
@ -269,9 +261,8 @@ object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransa
kryo.writeClassAndObject(output, obj.newNotary)
}
@Suppress("UNCHECKED_CAST")
override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction {
val inputs = kryo.readClassAndObject(input) as List<StateRef>
val inputs: List<StateRef> = uncheckedCast(kryo.readClassAndObject(input))
val notary = kryo.readClassAndObject(input) as Party
val newNotary = kryo.readClassAndObject(input) as Party
@ -286,78 +277,24 @@ object SignedTransactionSerializer : Serializer<SignedTransaction>() {
kryo.writeClassAndObject(output, obj.sigs)
}
@Suppress("UNCHECKED_CAST")
override fun read(kryo: Kryo, input: Input, type: Class<SignedTransaction>): SignedTransaction {
return SignedTransaction(
kryo.readClassAndObject(input) as SerializedBytes<CoreTransaction>,
kryo.readClassAndObject(input) as List<TransactionSignature>
uncheckedCast<Any?, SerializedBytes<CoreTransaction>>(kryo.readClassAndObject(input)),
uncheckedCast<Any?, List<TransactionSignature>>(kryo.readClassAndObject(input))
)
}
}
/** For serialising an ed25519 private key */
@ThreadSafe
object Ed25519PrivateKeySerializer : Serializer<EdDSAPrivateKey>() {
override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) {
check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec)
output.writeBytesWithLength(obj.seed)
}
override fun read(kryo: Kryo, input: Input, type: Class<EdDSAPrivateKey>): EdDSAPrivateKey {
val seed = input.readBytesWithLength()
return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec))
}
}
/** For serialising an ed25519 public key */
@ThreadSafe
object Ed25519PublicKeySerializer : Serializer<EdDSAPublicKey>() {
override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) {
check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec)
output.writeBytesWithLength(obj.abyte)
}
override fun read(kryo: Kryo, input: Input, type: Class<EdDSAPublicKey>): EdDSAPublicKey {
val A = input.readBytesWithLength()
return EdDSAPublicKey(EdDSAPublicKeySpec(A, Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec))
}
}
/** For serialising an ed25519 public key */
@ThreadSafe
object ECPublicKeyImplSerializer : Serializer<ECPublicKeyImpl>() {
override fun write(kryo: Kryo, output: Output, obj: ECPublicKeyImpl) {
output.writeBytesWithLength(obj.encoded)
}
override fun read(kryo: Kryo, input: Input, type: Class<ECPublicKeyImpl>): ECPublicKeyImpl {
val A = input.readBytesWithLength()
val der = DerValue(A)
return ECPublicKeyImpl.parse(der) as ECPublicKeyImpl
}
}
// TODO Implement standardized serialization of CompositeKeys. See JIRA issue: CORDA-249.
@ThreadSafe
object CompositeKeySerializer : Serializer<CompositeKey>() {
override fun write(kryo: Kryo, output: Output, obj: CompositeKey) {
output.writeInt(obj.threshold)
output.writeInt(obj.children.size)
obj.children.forEach { kryo.writeClassAndObject(output, it) }
}
override fun read(kryo: Kryo, input: Input, type: Class<CompositeKey>): CompositeKey {
val threshold = input.readInt()
val children = readListOfLength<CompositeKey.NodeAndWeight>(kryo, input, minLen = 2)
val builder = CompositeKey.Builder()
children.forEach { builder.addKey(it.node, it.weight) }
return builder.build(threshold) as CompositeKey
sealed class UseCaseSerializer<T>(private val allowedUseCases: EnumSet<SerializationContext.UseCase>) : Serializer<T>() {
protected fun checkUseCase() {
checkUseCase(allowedUseCases)
}
}
@ThreadSafe
object PrivateKeySerializer : Serializer<PrivateKey>() {
object PrivateKeySerializer : UseCaseSerializer<PrivateKey>(EnumSet.of(Storage, Checkpoint)) {
override fun write(kryo: Kryo, output: Output, obj: PrivateKey) {
checkUseCase()
output.writeBytesWithLength(obj.encoded)
}
@ -531,7 +468,7 @@ object LoggerSerializer : Serializer<Logger>() {
object ClassSerializer : Serializer<Class<*>>() {
override fun read(kryo: Kryo, input: Input, type: Class<Class<*>>): Class<*> {
val className = input.readString()
return Class.forName(className)
return Class.forName(className, true, kryo.classLoader)
}
override fun write(kryo: Kryo, output: Output, clazz: Class<*>) {
@ -608,8 +545,7 @@ class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>
}
}
@Suppress("UNCHECKED_CAST")
private val delegate: Serializer<Throwable> = ReflectionSerializerFactory.makeSerializer(kryo, FieldSerializer::class.java, type) as Serializer<Throwable>
private val delegate: Serializer<Throwable> = uncheckedCast(ReflectionSerializerFactory.makeSerializer(kryo, FieldSerializer::class.java, type))
override fun write(kryo: Kryo, output: Output, throwable: Throwable) {
delegate.write(kryo, output, throwable)
@ -617,7 +553,7 @@ class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>
override fun read(kryo: Kryo, input: Input, type: Class<Throwable>): Throwable {
val throwableRead = delegate.read(kryo, input, type)
if(throwableRead.suppressed.isEmpty()) {
if (throwableRead.suppressed.isEmpty()) {
throwableRead.setSuppressedToSentinel()
}
return throwableRead

View File

@ -1,17 +0,0 @@
package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import net.corda.core.serialization.SerializationCustomization
class KryoSerializationCustomization(val kryo: Kryo) : SerializationCustomization {
override fun addToWhitelist(vararg types: Class<*>) {
require(types.toSet().size == types.size) {
val duplicates = types.toMutableList()
types.toSet().forEach { duplicates -= it }
"Cannot add duplicate classes to the whitelist ($duplicates)."
}
for (type in types) {
((kryo.classResolver as? CordaClassResolver)?.whitelist as? MutableClassWhitelist)?.add(type)
}
}
}

View File

@ -14,6 +14,7 @@ import com.google.common.cache.CacheBuilder
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.LazyPool
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
@ -26,7 +27,7 @@ import java.util.concurrent.ExecutionException
val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled"
object NotSupportedSeralizationScheme : SerializationScheme {
object NotSupportedSerializationScheme : SerializationScheme {
private fun doThrow(): Nothing = throw UnsupportedOperationException("Serialization scheme not supported.")
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean = doThrow()
@ -51,7 +52,7 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
* We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context.
*/
override fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): SerializationContext {
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean ?: false || return this
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this
val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContextImpl ?: return this // Some tests don't set one.
try {
return withClassLoader(cache.get(attachmentHashes) {
@ -106,7 +107,7 @@ open class SerializationFactoryImpl : SerializationFactory() {
registeredSchemes
.filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) }
.forEach { return@computeIfAbsent it }
NotSupportedSeralizationScheme
NotSupportedSerializationScheme
}
}
@ -141,8 +142,8 @@ open class SerializationFactoryImpl : SerializationFactory() {
private object AutoCloseableSerialisationDetector : Serializer<AutoCloseable>() {
override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) {
val message = "${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " +
"Restoring such resources across node restarts is not supported. Make sure code accessing it is " +
"confined to a private method or the reference is nulled out."
"Restoring such resources across node restarts is not supported. Make sure code accessing it is " +
"confined to a private method or the reference is nulled out."
throw UnsupportedOperationException(message)
}
@ -204,11 +205,10 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
Input(byteSequence.bytes, byteSequence.offset + headerSize, byteSequence.size - headerSize).use { input ->
return pool.run { kryo ->
withContext(kryo, context) {
@Suppress("UNCHECKED_CAST")
if (context.objectReferencesEnabled) {
kryo.readClassAndObject(input) as T
uncheckedCast(kryo.readClassAndObject(input))
} else {
kryo.withoutReferences { kryo.readClassAndObject(input) as T }
kryo.withoutReferences { uncheckedCast<Any?, T>(kryo.readClassAndObject(input)) }
}
}
}

View File

@ -1,4 +1,5 @@
@file:JvmName("ServerContexts")
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.ClassWhitelist

View File

@ -0,0 +1,12 @@
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import java.util.*
internal fun checkUseCase(allowedUseCases: EnumSet<SerializationContext.UseCase>) {
val currentContext: SerializationContext = SerializationFactory.currentFactory?.currentContext ?: throw IllegalStateException("Current context is not set")
if (!allowedUseCases.contains(currentContext.useCase)) {
throw IllegalStateException("UseCase '${currentContext.useCase}' is not within '$allowedUseCases'")
}
}

View File

@ -23,14 +23,13 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
//
// We *need* to retain knowledge for AMQP deserialisation weather that lowest primitive
// was boxed or unboxed so just infer it recursively
private fun calcTypeName(type: Type) : String =
if (type.componentType().isArray()) {
val typeName = calcTypeName(type.componentType()); "$typeName[]"
}
else {
val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]"
"${type.componentType().typeName}$arrayType"
}
private fun calcTypeName(type: Type): String =
if (type.componentType().isArray()) {
val typeName = calcTypeName(type.componentType()); "$typeName[]"
} else {
val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]"
"${type.componentType().typeName}$arrayType"
}
override val typeDescriptor by lazy { Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}") }
internal val elementType: Type by lazy { type.componentType() }

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NonEmptySet
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
@ -34,12 +35,10 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
}
fun deriveParameterizedType(declaredType: Type, declaredClass: Class<*>, actualClass: Class<*>?): ParameterizedType {
if(supportedTypes.containsKey(declaredClass)) {
if (supportedTypes.containsKey(declaredClass)) {
// Simple case - it is already known to be a collection.
@Suppress("UNCHECKED_CAST")
return deriveParametrizedType(declaredType, declaredClass as Class<out Collection<*>>)
}
else if (actualClass != null && Collection::class.java.isAssignableFrom(actualClass)) {
return deriveParametrizedType(declaredType, uncheckedCast(declaredClass))
} else if (actualClass != null && Collection::class.java.isAssignableFrom(actualClass)) {
// Declared class is not collection, but [actualClass] is - represent it accordingly.
val collectionClass = findMostSuitableCollectionType(actualClass)
return deriveParametrizedType(declaredType, collectionClass)
@ -49,11 +48,11 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
}
private fun deriveParametrizedType(declaredType: Type, collectionClass: Class<out Collection<*>>): ParameterizedType =
(declaredType as? ParameterizedType) ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType))
(declaredType as? ParameterizedType) ?: DeserializedParameterizedType(collectionClass, arrayOf(SerializerFactory.AnyType))
private fun findMostSuitableCollectionType(actualClass: Class<*>): Class<out Collection<*>> =
supportedTypes.keys.findLast { it.isAssignableFrom(actualClass) }!!
supportedTypes.keys.findLast { it.isAssignableFrom(actualClass) }!!
}
@ -61,13 +60,13 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "list", Descriptor(typeDescriptor), emptyList())
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({declaredType.typeName}) {
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
if (output.writeTypeNotations(typeNotation)) {
output.requireSerializer(declaredType.actualTypeArguments[0])
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({declaredType.typeName}) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
// Write described
data.withDescribed(typeNotation.descriptor) {
withList {
@ -78,7 +77,7 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
}
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({declaredType.typeName}) {
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
// TODO: Can we verify the entries in the list?
concreteBuilder((obj as List<*>).map { input.readObjectOrNull(it, schema, declaredType.actualTypeArguments[0]) })
}

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
@ -9,7 +10,7 @@ import java.lang.reflect.Type
* Base class for serializers of core platform types that do not conform to the usual serialization rules and thus
* cannot be automatically serialized.
*/
abstract class CustomSerializer<T> : AMQPSerializer<T> {
abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
/**
* This is a collection of custom serializers that this custom serializer depends on. e.g. for proxy objects
* that refer to other custom types etc.
@ -36,8 +37,7 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
data.withDescribed(descriptor) {
@Suppress("UNCHECKED_CAST")
writeDescribedObject(obj as T, data, type, output)
writeDescribedObject(uncheckedCast(obj), data, type, output)
}
}
@ -49,7 +49,7 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* subclass in the schema, so that we can distinguish between subclasses.
*/
// TODO: should this be a custom serializer at all, or should it just be a plain AMQPSerializer?
class SubClass<T>(protected val clazz: Class<*>, protected val superClassSerializer: CustomSerializer<T>) : CustomSerializer<T>() {
class SubClass<T : Any>(protected val clazz: Class<*>, protected val superClassSerializer: CustomSerializer<T>) : CustomSerializer<T>() {
// TODO: should this be empty or contain the schema of the super?
override val schemaForDocumentation = Schema(emptyList())
@ -76,7 +76,7 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* Additional base features for a custom serializer for a particular class [withInheritance] is false
* or super class / interfaces [withInheritance] is true
*/
abstract class CustomSerializerImp<T>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() {
abstract class CustomSerializerImp<T : Any>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() {
override val type: Type get() = clazz
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(clazz)}")
override fun writeClassInfo(output: SerializationOutput) {}
@ -87,12 +87,12 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
/**
* Additional base features for a custom serializer for a particular class, that excludes subclasses.
*/
abstract class Is<T>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, false)
abstract class Is<T : Any>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, false)
/**
* Additional base features for a custom serializer for all implementations of a particular interface or super class.
*/
abstract class Implements<T>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, true)
abstract class Implements<T : Any>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, true)
/**
* Additional base features over and above [Implements] or [Is] custom serializer for when the serialized form should be
@ -101,10 +101,10 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* The proxy class must use only types which are either native AMQP or other types for which there are pre-registered
* custom serializers.
*/
abstract class Proxy<T, P>(clazz: Class<T>,
protected val proxyClass: Class<P>,
protected val factory: SerializerFactory,
withInheritance: Boolean = true) : CustomSerializerImp<T>(clazz, withInheritance) {
abstract class Proxy<T : Any, P : Any>(clazz: Class<T>,
protected val proxyClass: Class<P>,
protected val factory: SerializerFactory,
withInheritance: Boolean = true) : CustomSerializerImp<T>(clazz, withInheritance) {
override fun isSerializerFor(clazz: Class<*>): Boolean = if (withInheritance) this.clazz.isAssignableFrom(clazz) else this.clazz == clazz
private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(proxyClass, factory) }
@ -134,8 +134,7 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): T {
@Suppress("UNCHECKED_CAST")
val proxy = proxySerializer.readObject(obj, schema, input) as P
val proxy: P = uncheckedCast(proxySerializer.readObject(obj, schema, input))
return fromProxy(proxy)
}
}
@ -151,12 +150,11 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
* @param make A lambda for constructing an instance, that defaults to calling a constructor that expects a string.
* @param unmake A lambda that extracts the string value for an instance, that defaults to the [toString] method.
*/
abstract class ToString<T>(clazz: Class<T>, withInheritance: Boolean = false,
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let {
`constructor` ->
{ string -> `constructor`.newInstance(string) }
},
private val unmaker: (T) -> String = { obj -> obj.toString() })
abstract class ToString<T : Any>(clazz: Class<T>, withInheritance: Boolean = false,
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` ->
{ string -> `constructor`.newInstance(string) }
},
private val unmaker: (T) -> String = { obj -> obj.toString() })
: CustomSerializerImp<T>(clazz, withInheritance) {
override val schemaForDocumentation = Schema(

View File

@ -11,6 +11,8 @@ import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
import java.lang.reflect.WildcardType
import java.nio.ByteBuffer
data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope)
@ -58,7 +60,7 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
deserializeAndReturnEnvelope(bytes, T::class.java)
@Throws(NotSerializableException::class)
private fun getEnvelope(bytes: ByteSequence): Envelope {
internal fun getEnvelope(bytes: ByteSequence): Envelope {
// Check that the lead bytes match expected header
val headerSize = AmqpHeaderV1_0.size
if (bytes.take(headerSize) != AmqpHeaderV1_0) {
@ -78,9 +80,9 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
private fun <R> des(generator: () -> R): R {
try {
return generator()
} catch(nse: NotSerializableException) {
} catch (nse: NotSerializableException) {
throw nse
} catch(t: Throwable) {
} catch (t: Throwable) {
throw NotSerializableException("Unexpected throwable: ${t.message} ${t.getStackTraceAsString()}")
} finally {
objectHistory.clear()
@ -142,10 +144,18 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
}
/**
* TODO: Currently performs rather basic checks aimed in particular at [java.util.List<Command<?>>] and
* [java.lang.Class<? extends net.corda.core.contracts.Contract>]
* Currently performs checks aimed at:
* * [java.util.List<Command<?>>] and [java.lang.Class<? extends net.corda.core.contracts.Contract>]
* * [T : Parent] and [Parent]
* * [? extends Parent] and [Parent]
*
* In the future tighter control might be needed
*/
private fun Type.materiallyEquivalentTo(that: Type): Boolean =
asClass() == that.asClass() && that is ParameterizedType
when (that) {
is ParameterizedType -> asClass() == that.asClass()
is TypeVariable<*> -> isSubClassOf(that.bounds.first())
is WildcardType -> isSubClassOf(that.upperBounds.first())
else -> false
}
}

View File

@ -30,9 +30,9 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
val enumName = (obj as List<*>)[0] as String
val enumOrd = obj[1] as Int
val fromOrd = type.asClass()!!.enumConstants[enumOrd]
val fromOrd = type.asClass()!!.enumConstants[enumOrd] as Enum<*>?
if (enumName != fromOrd?.toString()) {
if (enumName != fromOrd?.name) {
throw NotSerializableException("Deserializing obj as enum $type with value $enumName.$enumOrd but "
+ "ordinality has changed")
}

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
@ -31,8 +32,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
LinkedHashMap::class.java to { map -> LinkedHashMap(map) },
TreeMap::class.java to { map -> TreeMap(map) },
EnumMap::class.java to { map ->
@Suppress("UNCHECKED_CAST")
EnumMap(map as Map<EnumJustUsedForCasting, Any>)
EnumMap(uncheckedCast<Map<*, *>, Map<EnumJustUsedForCasting, Any>>(map))
}
))
@ -42,12 +42,10 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
fun deriveParameterizedType(declaredType: Type, declaredClass: Class<*>, actualClass: Class<*>?): ParameterizedType {
declaredClass.checkSupportedMapType()
if(supportedTypes.containsKey(declaredClass)) {
if (supportedTypes.containsKey(declaredClass)) {
// Simple case - it is already known to be a map.
@Suppress("UNCHECKED_CAST")
return deriveParametrizedType(declaredType, declaredClass as Class<out Map<*, *>>)
}
else if (actualClass != null && Map::class.java.isAssignableFrom(actualClass)) {
return deriveParametrizedType(declaredType, uncheckedCast(declaredClass))
} else if (actualClass != null && Map::class.java.isAssignableFrom(actualClass)) {
// Declared class is not map, but [actualClass] is - represent it accordingly.
val mapClass = findMostSuitableMapType(actualClass)
return deriveParametrizedType(declaredType, mapClass)
@ -68,14 +66,14 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "map", Descriptor(typeDescriptor), emptyList())
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({declaredType.typeName}) {
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
if (output.writeTypeNotations(typeNotation)) {
output.requireSerializer(declaredType.actualTypeArguments[0])
output.requireSerializer(declaredType.actualTypeArguments[1])
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({declaredType.typeName}) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) {
obj.javaClass.checkSupportedMapType()
// Write described
data.withDescribed(typeNotation.descriptor) {
@ -90,7 +88,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
}
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({declaredType.typeName}) {
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
// TODO: General generics question. Do we need to validate that entries in Maps and Collections match the generic type? Is it a security hole?
val entries: Iterable<Pair<Any?, Any?>> = (obj as Map<*, *>).map { readEntry(schema, input, it) }
concreteBuilder(entries.toMap())
@ -109,13 +107,11 @@ internal fun Class<*>.checkSupportedMapType() {
if (HashMap::class.java.isAssignableFrom(this) && !LinkedHashMap::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException(
"Map type $this is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
}
else if (WeakHashMap::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException ("Weak references with map types not supported. Suggested fix: "
+ "use java.util.LinkedHashMap instead.")
}
else if (Dictionary::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException (
} else if (WeakHashMap::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException("Weak references with map types not supported. Suggested fix: "
+ "use java.util.LinkedHashMap instead.")
} else if (Dictionary::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException(
"Unable to serialise deprecated type $this. Suggested fix: prefer java.util.map implementations")
}
}

View File

@ -41,7 +41,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({clazz.typeName}) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ clazz.typeName }) {
// Write described
data.withDescribed(typeNotation.descriptor) {
// Write list
@ -53,7 +53,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
}
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({clazz.typeName}) {
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) {
if (obj is List<*>) {
if (obj.size > propertySerializers.size) throw NotSerializableException("Too many properties in described type $typeName")
val params = obj.zip(propertySerializers).map { it.second.readProperty(it.first, schema, input) }

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.loggerFor
import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Method
@ -20,8 +21,8 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
val default: String? = generateDefault()
val mandatory: Boolean = generateMandatory()
private val isInterface: Boolean get() = resolvedType.asClass()?.isInterface ?: false
private val isJVMPrimitive: Boolean get() = resolvedType.asClass()?.isPrimitive ?: false
private val isInterface: Boolean get() = resolvedType.asClass()?.isInterface == true
private val isJVMPrimitive: Boolean get() = resolvedType.asClass()?.isPrimitive == true
private fun generateType(): String {
return if (isInterface || resolvedType == Any::class.java) "*" else SerializerFactory.nameForType(resolvedType)
@ -44,19 +45,28 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
}
private fun generateMandatory(): Boolean {
return isJVMPrimitive || !(readMethod?.returnsNullable() ?: true)
return isJVMPrimitive || readMethod?.returnsNullable() == false
}
private fun Method.returnsNullable(): Boolean {
val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { it.javaGetter == this }?.returnType?.toString() ?: "?"
return returnTypeString.endsWith('?') || returnTypeString.endsWith('!')
try {
val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { it.javaGetter == this }?.returnType?.toString() ?: "?"
return returnTypeString.endsWith('?') || returnTypeString.endsWith('!')
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
// TODO: Revisit this when Kotlin issue is fixed.
logger.error("Unexpected internal Kotlin error", e)
return true
}
}
companion object {
private val logger = loggerFor<PropertySerializer>()
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
readMethod?.isAccessible = true
if (SerializerFactory.isPrimitive(resolvedType)) {
return when(resolvedType) {
return when (resolvedType) {
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
else -> AMQPPrimitivePropertySerializer(name, readMethod, resolvedType)
}
@ -76,17 +86,17 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
// This is lazy so we don't get an infinite loop when a method returns an instance of the class.
private val typeSerializer: AMQPSerializer<*> by lazy { lazyTypeSerializer() }
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({nameForDebug}) {
override fun writeClassInfo(output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
if (resolvedType != Any::class.java) {
typeSerializer.writeClassInfo(output)
}
}
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? = ifThrowsAppend({nameForDebug}) {
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
input.readObjectOrNull(obj, schema, resolvedType)
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({nameForDebug}) {
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
output.writeObjectOrNull(readMethod!!.invoke(obj), data, resolvedType)
}
@ -123,7 +133,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
override fun writeClassInfo(output: SerializationOutput) {}
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? {
return if(obj == null) null else (obj as Short).toChar()
return if (obj == null) null else (obj as Short).toChar()
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.hash.Hasher
import com.google.common.hash.Hashing
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toBase64
@ -94,8 +95,7 @@ data class Schema(val types: List<TypeNotation>) : DescribedType {
override fun newInstance(described: Any?): Schema {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
@Suppress("UNCHECKED_CAST")
return Schema(list[0] as List<TypeNotation>)
return Schema(uncheckedCast(list[0]))
}
}
@ -162,8 +162,7 @@ data class Field(val name: String, val type: String, val requires: List<String>,
override fun newInstance(described: Any?): Field {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
@Suppress("UNCHECKED_CAST")
return Field(list[0] as String, list[1] as String, list[2] as List<String>, list[3] as? String, list[4] as? String, list[5] as Boolean, list[6] as Boolean)
return Field(list[0] as String, list[1] as String, uncheckedCast(list[2]), list[3] as? String, list[4] as? String, list[5] as Boolean, list[6] as Boolean)
}
}
@ -223,8 +222,7 @@ data class CompositeType(override val name: String, override val label: String?,
override fun newInstance(described: Any?): CompositeType {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
@Suppress("UNCHECKED_CAST")
return CompositeType(list[0] as String, list[1] as? String, list[2] as List<String>, list[3] as Descriptor, list[4] as List<Field>)
return CompositeType(list[0] as String, list[1] as? String, uncheckedCast(list[2]), list[3] as Descriptor, uncheckedCast(list[4]))
}
}
@ -273,8 +271,7 @@ data class RestrictedType(override val name: String,
override fun newInstance(described: Any?): RestrictedType {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
@Suppress("UNCHECKED_CAST")
return RestrictedType(list[0] as String, list[1] as? String, list[2] as List<String>, list[3] as String, list[4] as Descriptor, list[5] as List<Choice>)
return RestrictedType(list[0] as String, list[1] as? String, uncheckedCast(list[2]), list[3] as String, list[4] as Descriptor, uncheckedCast(list[5]))
}
}

View File

@ -1,5 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeToken
import net.corda.core.serialization.SerializationContext
@ -51,7 +53,7 @@ internal fun constructorForDeserialization(type: Type): KFunction<Any>? {
}
}
return preferredCandidate?.apply { isAccessible = true}
return preferredCandidate?.apply { isAccessible = true }
?: throw NotSerializableException("No constructor for deserialization found for $clazz.")
} else {
return null
@ -80,8 +82,8 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
for (param in kotlinConstructor.parameters) {
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
val matchingProperty = properties[name] ?:
throw NotSerializableException("No property matching constructor parameter named $name of $clazz." +
" If using Java, check that you have the -parameters option specified in the Java compiler.")
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'." +
" If using Java, check that you have the -parameters option specified in the Java compiler.")
// Check that the method has a getter in java.
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.")
@ -123,7 +125,7 @@ private fun exploreType(type: Type?, interfaces: MutableSet<Type>, serializerFac
val clazz = type?.asClass()
if (clazz != null) {
if (clazz.isInterface) {
if(serializerFactory.isNotWhitelisted(clazz)) return // We stop exploring once we reach a branch that has no `CordaSerializable` annotation or whitelisting.
if (serializerFactory.whitelist.isNotWhitelisted(clazz)) return // We stop exploring once we reach a branch that has no `CordaSerializable` annotation or whitelisting.
else interfaces += type
}
for (newInterface in clazz.genericInterfaces) {
@ -189,6 +191,8 @@ internal fun Type.asClass(): Class<*>? {
this is Class<*> -> this
this is ParameterizedType -> this.rawType.asClass()
this is GenericArrayType -> this.genericComponentType.asClass()?.arrayClass()
this is TypeVariable<*> -> this.bounds.first().asClass()
this is WildcardType -> this.upperBounds.first().asClass()
else -> null
}
}
@ -261,3 +265,19 @@ private fun Throwable.setMessage(newMsg: String) {
detailMessageField.isAccessible = true
detailMessageField.set(this, newMsg)
}
fun ClassWhitelist.requireWhitelisted(type: Type) {
if (!this.isWhitelisted(type.asClass()!!)) {
throw NotSerializableException("Class $type is not on the whitelist or annotated with @CordaSerializable.")
}
}
fun ClassWhitelist.isWhitelisted(clazz: Class<*>) = (hasListed(clazz) || hasAnnotationInHierarchy(clazz))
fun ClassWhitelist.isNotWhitelisted(clazz: Class<*>) = !(this.isWhitelisted(clazz))
// Recursively check the class, interfaces and superclasses for our annotation.
fun ClassWhitelist.hasAnnotationInHierarchy(type: Class<*>): Boolean {
return type.isAnnotationPresent(CordaSerializable::class.java)
|| type.interfaces.any { hasAnnotationInHierarchy(it) }
|| (type.superclass != null && hasAnnotationInHierarchy(type.superclass))
}

View File

@ -82,14 +82,13 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
}
val retrievedRefCount = objectHistory[obj]
if(retrievedRefCount == null) {
if (retrievedRefCount == null) {
serializer.writeObject(obj, data, type, this)
// Important to do it after serialization such that dependent object will have preceding reference numbers
// assigned to them first as they will be first read from the stream on receiving end.
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
if (suitableForObjectReference(obj.javaClass)) objectHistory.put(obj, objectHistory.size)
}
else {
} else {
data.writeReferencedObject(ReferencedObject(retrievedRefCount))
}
}

View File

@ -2,8 +2,8 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.primitives.Primitives
import com.google.common.reflect.TypeResolver
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.serialization.carpenter.*
import org.apache.qpid.proton.amqp.*
import java.io.NotSerializableException
@ -30,11 +30,11 @@ data class FactorySchemaAndDescriptor(val schema: Schema, val typeDescriptor: An
// TODO: need to rethink matching of constructor to properties in relation to implementing interfaces and needing those properties etc.
// TODO: need to support super classes as well as interfaces with our current code base... what's involved? If we continue to ban, what is the impact?
@ThreadSafe
class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
val classCarpenter = ClassCarpenter(cl, whitelist)
open val classCarpenter = ClassCarpenter(cl, whitelist)
val classloader: ClassLoader
get() = classCarpenter.classloader
@ -81,6 +81,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
}
}
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> serializersByType.computeIfAbsent(actualClass ?: declaredClass) {
whitelist.requireWhitelisted(actualType)
EnumSerializer(actualType, actualClass ?: declaredClass, this)
}
else -> makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
@ -105,6 +106,8 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
val declaredComponent = declaredType.genericComponentType
inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray()
}
is TypeVariable<*> -> actualClass
is WildcardType -> actualClass
else -> null
}
@ -239,10 +242,10 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
if (clazz.componentType.isPrimitive) PrimArraySerializer.make(type, this)
else ArraySerializer.make(type, this)
} else if (clazz.kotlin.objectInstance != null) {
whitelisted(clazz)
whitelist.requireWhitelisted(clazz)
SingletonSerializer(clazz, clazz.kotlin.objectInstance!!, this)
} else {
whitelisted(type)
whitelist.requireWhitelisted(type)
ObjectSerializer(type, this)
}
}
@ -260,32 +263,13 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
return customSerializer
} else {
// Make a subclass serializer for the subclass and return that...
@Suppress("UNCHECKED_CAST")
return CustomSerializer.SubClass(clazz, customSerializer as CustomSerializer<Any>)
return CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer))
}
}
}
return null
}
private fun whitelisted(type: Type) {
val clazz = type.asClass()!!
if (isNotWhitelisted(clazz)) {
throw NotSerializableException("Class $type is not on the whitelist or annotated with @CordaSerializable.")
}
}
// Ignore SimpleFieldAccess as we add it to anything we build in the carpenter.
internal fun isNotWhitelisted(clazz: Class<*>): Boolean = clazz == SimpleFieldAccess::class.java ||
(!whitelist.hasListed(clazz) && !hasAnnotationInHierarchy(clazz))
// Recursively check the class, interfaces and superclasses for our annotation.
private fun hasAnnotationInHierarchy(type: Class<*>): Boolean {
return type.isAnnotationPresent(CordaSerializable::class.java) ||
type.interfaces.any { hasAnnotationInHierarchy(it) }
|| (type.superclass != null && hasAnnotationInHierarchy(type.superclass))
}
private fun makeMapSerializer(declaredType: ParameterizedType): AMQPSerializer<Any> {
val rawType = declaredType.rawType as Class<*>
rawType.checkSupportedMapType()

View File

@ -1,11 +1,11 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.internal.uncheckedCast
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.MapSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.util.*
@Suppress("UNCHECKED_CAST")
/**
* A serializer that writes out an [EnumSet] as a type, plus list of instances in the set.
*/
@ -16,7 +16,7 @@ class EnumSetSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Enu
private fun elementType(set: EnumSet<*>): Class<*> {
return if (set.isEmpty()) {
EnumSet.complementOf(set as EnumSet<MapSerializer.EnumJustUsedForCasting>).first().javaClass
EnumSet.complementOf(uncheckedCast<EnumSet<*>, EnumSet<MapSerializer.EnumJustUsedForCasting>>(set)).first().javaClass
} else {
set.first().javaClass
}
@ -24,9 +24,9 @@ class EnumSetSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Enu
override fun fromProxy(proxy: EnumSetProxy): EnumSet<*> {
return if (proxy.elements.isEmpty()) {
EnumSet.noneOf(proxy.clazz as Class<MapSerializer.EnumJustUsedForCasting>)
EnumSet.noneOf(uncheckedCast<Class<*>, Class<MapSerializer.EnumJustUsedForCasting>>(proxy.clazz))
} else {
EnumSet.copyOf(proxy.elements as List<MapSerializer.EnumJustUsedForCasting>)
EnumSet.copyOf(uncheckedCast<List<Any>, List<MapSerializer.EnumJustUsedForCasting>>(proxy.elements))
}
}

View File

@ -0,0 +1,27 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.nodeapi.internal.serialization.amqp.*
import net.corda.nodeapi.internal.serialization.checkUseCase
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.security.PrivateKey
import java.util.*
object PrivateKeySerializer : CustomSerializer.Implements<PrivateKey>(PrivateKey::class.java) {
private val allowedUseCases = EnumSet.of(Storage, Checkpoint)
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: PrivateKey, data: Data, type: Type, output: SerializationOutput) {
checkUseCase(allowedUseCases)
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): PrivateKey {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return Crypto.decodePrivateKey(bits)
}
}

View File

@ -27,7 +27,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
for (prop in props) {
extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
}
} catch(e: NotSerializableException) {
} catch (e: NotSerializableException) {
logger.warn("Unexpected exception", e)
}
obj.originalMessage
@ -67,7 +67,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
logger.warn("Unexpected exception de-serializing throwable: ${proxy.exceptionClass}. Converting to CordaRuntimeException.", e)
}
// If the criteria are not met or we experience an exception constructing the exception, we fall back to our own unchecked exception.
return CordaRuntimeException(proxy.exceptionClass).apply {
return CordaRuntimeException(proxy.exceptionClass, null, null).apply {
this.setMessage(proxy.message)
this.setCause(proxy.cause)
this.stackTrace = proxy.stackTrace

View File

@ -12,10 +12,12 @@ class ZonedDateTimeSerializer(factory: SerializerFactory) : CustomSerializer.Pro
// so that any change to internals of `ZonedDateTime` is detected early.
companion object {
val ofLenient = ZonedDateTime::class.java.getDeclaredMethod("ofLenient", LocalDateTime::class.java, ZoneOffset::class.java, ZoneId::class.java)
init {
ofLenient.isAccessible = true
}
}
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(LocalDateTimeSerializer(factory), ZoneIdSerializer(factory))
override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone)

View File

@ -132,5 +132,10 @@ fun AMQPField.validateType(classloader: ClassLoader) = when (type) {
}
private fun ClassLoader.exists(clazz: String) = run {
try { this.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } }
try {
this.loadClass(clazz); true
} catch (e: ClassNotFoundException) {
false
}
}

View File

@ -6,10 +6,8 @@ import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import java.lang.Character.isJavaIdentifierPart
import java.lang.Character.isJavaIdentifierStart
import java.util.*
/**
@ -17,6 +15,7 @@ import java.util.*
* as if `this.class.getMethod("get" + name.capitalize()).invoke(this)` had been called. It is intended as a more
* convenient alternative to reflection.
*/
@CordaSerializable
interface SimpleFieldAccess {
operator fun get(name: String): Any?
}
@ -134,7 +133,10 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
visit(TARGET_VERSION, ACC_PUBLIC + ACC_FINAL + ACC_SUPER + ACC_ENUM, schema.jvmName,
"L$jlEnum<L${schema.jvmName};>;", jlEnum, null)
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
if (schema.flags.cordaSerializable()) {
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
}
generateFields(schema)
generateStaticEnumConstructor(schema)
generateEnumConstructor()
@ -151,8 +153,10 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
cw.apply {
visit(TARGET_VERSION, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, schema.jvmName, null,
jlObject, interfaces)
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
if (schema.flags.cordaSerializable()) {
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
}
generateAbstractGetters(schema)
}.visitEnd()
}
@ -163,20 +167,25 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
val superName = schema.superclass?.jvmName ?: jlObject
val interfaces = schema.interfaces.map { it.name.jvm }.toMutableList()
if (SimpleFieldAccess::class.java !in schema.interfaces) {
if (SimpleFieldAccess::class.java !in schema.interfaces
&& schema.flags.cordaSerializable()
&& schema.flags.simpleFieldAccess()) {
interfaces.add(SimpleFieldAccess::class.java.name.jvm)
}
cw.apply {
visit(TARGET_VERSION, ACC_PUBLIC + ACC_SUPER, schema.jvmName, null, superName,
interfaces.toTypedArray())
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
if (schema.flags.cordaSerializable()) {
visitAnnotation(Type.getDescriptor(CordaSerializable::class.java), true).visitEnd()
}
generateFields(schema)
generateClassConstructor(schema)
generateGetters(schema)
if (schema.superclass == null)
if (schema.superclass == null) {
generateGetMethod() // From SimplePropertyAccess
}
generateToString(schema)
}.visitEnd()
}
@ -237,10 +246,9 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
}
private fun ClassWriter.generateGetters(schema: Schema) {
@Suppress("UNCHECKED_CAST")
for ((name, type) in (schema.fields as Map<String, ClassField>)) {
for ((name, type) in schema.fields) {
visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + type.descriptor, null, null).apply {
type.addNullabilityAnnotation(this)
(type as ClassField).addNullabilityAnnotation(this)
visitCode()
visitVarInsn(ALOAD, 0) // Load 'this'
visitFieldInsn(GETFIELD, schema.jvmName, name, type.descriptor)
@ -258,8 +266,7 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
}
private fun ClassWriter.generateAbstractGetters(schema: Schema) {
@Suppress("UNCHECKED_CAST")
for ((name, field) in (schema.fields as Map<String, ClassField>)) {
for ((name, field) in schema.fields) {
val opcodes = ACC_ABSTRACT + ACC_PUBLIC
// abstract method doesn't have any implementation so just end
visitMethod(opcodes, "get" + name.capitalize(), "()${field.descriptor}", null, null).visitEnd()
@ -363,9 +370,8 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
// Assign the fields from parameters.
var slot = 1 + superclassFields.size
@Suppress("UNCHECKED_CAST")
for ((name, field) in (schema.fields as Map<String, ClassField>)) {
field.nullTest(this, slot)
for ((name, field) in schema.fields) {
(field as ClassField).nullTest(this, slot)
visitVarInsn(ALOAD, 0) // Load 'this' onto the stack
slot += load(slot, field) // Load the contents of the parameter onto the stack.
@ -391,11 +397,21 @@ class ClassCarpenter(cl: ClassLoader = Thread.currentThread().contextClassLoader
}
}
/**
* If a sub element isn't whitelist we will not build a class containing that type as a member. Since, by
* default, classes created by the [ClassCarpenter] are annotated as [CordaSerializable] we will always
* be able to carpent classes generated from our AMQP library as, at a base level, we will either be able to
* create the lowest level in the meta hierarchy because either all members are jvm primitives or
* whitelisted classes
*/
private fun validateSchema(schema: Schema) {
if (schema.name in _loaded) throw DuplicateNameException()
fun isJavaName(n: String) = n.isNotBlank() && isJavaIdentifierStart(n.first()) && n.all(::isJavaIdentifierPart)
require(isJavaName(schema.name.split(".").last())) { "Not a valid Java name: ${schema.name}" }
schema.fields.keys.forEach { require(isJavaName(it)) { "Not a valid Java name: $it" } }
schema.fields.forEach {
require(isJavaName(it.key)) { "Not a valid Java name: $it" }
}
// Now check each interface we've been asked to implement, as the JVM will unfortunately only catch the
// fact that we didn't implement the interface we said we would at the moment the missing method is
// actually called, which is a bit too dynamic for my tastes.

View File

@ -1,11 +1,14 @@
package net.corda.nodeapi.internal.serialization.carpenter
class DuplicateNameException : RuntimeException(
import net.corda.core.CordaException
import net.corda.core.CordaRuntimeException
class DuplicateNameException : CordaRuntimeException(
"An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.")
class InterfaceMismatchException(msg: String) : RuntimeException(msg)
class InterfaceMismatchException(msg: String) : CordaRuntimeException(msg)
class NullablePrimitiveException(msg: String) : RuntimeException(msg)
class NullablePrimitiveException(msg: String) : CordaRuntimeException(msg)
class UncarpentableException(name: String, field: String, type: String) :
Exception("Class $name is loadable yet contains field $field of unknown type $type")
CordaException("Class $name is loadable yet contains field $field of unknown type $type")

View File

@ -1,8 +1,12 @@
package net.corda.nodeapi.internal.serialization.carpenter
import kotlin.collections.LinkedHashMap
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes.*
import java.util.*
enum class SchemaFlags {
SimpleFieldAccess, CordaSerializable
}
/**
* A Schema is the representation of an object the Carpenter can contsruct
@ -20,6 +24,8 @@ abstract class Schema(
updater: (String, Field) -> Unit) {
private fun Map<String, Field>.descriptors() = LinkedHashMap(this.mapValues { it.value.descriptor })
var flags: EnumMap<SchemaFlags, Boolean> = EnumMap(SchemaFlags::class.java)
init {
fields.forEach { updater(it.key, it.value) }
@ -41,6 +47,18 @@ abstract class Schema(
val asArray: String
get() = "[L$jvmName;"
fun unsetCordaSerializable() {
flags.replace(SchemaFlags.CordaSerializable, false)
}
}
fun EnumMap<SchemaFlags, Boolean>.cordaSerializable(): Boolean {
return this.getOrDefault(SchemaFlags.CordaSerializable, true) == true
}
fun EnumMap<SchemaFlags, Boolean>.simpleFieldAccess(): Boolean {
return this.getOrDefault(SchemaFlags.SimpleFieldAccess, true) == true
}
/**

View File

@ -1,2 +0,0 @@
# Register a ServiceLoader service extending from net.corda.core.node.CordaPluginRegistry
net.corda.nodeapi.internal.serialization.DefaultWhitelist

View File

@ -0,0 +1,135 @@
package net.corda.nodeapi.internal.serialization.amqp;
import net.corda.core.serialization.CordaSerializable;
import net.corda.core.serialization.SerializedBytes;
import net.corda.nodeapi.internal.serialization.AllWhitelist;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class ListsSerializationJavaTest {
@CordaSerializable
interface Parent {
}
public static class Child implements Parent {
private final int value;
Child(int value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Child child = (Child) o;
return value == child.value;
}
@Override
public int hashCode() {
return value;
}
// Needed to show that there is a property called "value"
@SuppressWarnings("unused")
public int getValue() {
return value;
}
}
@CordaSerializable
public static class CovariantContainer<T extends Parent> {
private final List<T> content;
CovariantContainer(List<T> content) {
this.content = content;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CovariantContainer<T> that = (CovariantContainer<T>) o;
return content != null ? content.equals(that.content) : that.content == null;
}
@Override
public int hashCode() {
return content != null ? content.hashCode() : 0;
}
// Needed to show that there is a property called "content"
@SuppressWarnings("unused")
public List<T> getContent() {
return content;
}
}
@CordaSerializable
public static class CovariantContainer2 {
private final List<? extends Parent> content;
CovariantContainer2(List<? extends Parent> content) {
this.content = content;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CovariantContainer2 that = (CovariantContainer2) o;
return content != null ? content.equals(that.content) : that.content == null;
}
@Override
public int hashCode() {
return content != null ? content.hashCode() : 0;
}
// Needed to show that there is a property called "content"
@SuppressWarnings("unused")
public List<? extends Parent> getContent() {
return content;
}
}
@Test
public void checkCovariance() throws Exception {
List<Child> payload = new ArrayList<>();
payload.add(new Child(1));
payload.add(new Child(2));
CovariantContainer<Child> container = new CovariantContainer<>(payload);
assertEqualAfterRoundTripSerialization(container, CovariantContainer.class);
}
@Test
public void checkCovariance2() throws Exception {
List<Child> payload = new ArrayList<>();
payload.add(new Child(1));
payload.add(new Child(2));
CovariantContainer2 container = new CovariantContainer2(payload);
assertEqualAfterRoundTripSerialization(container, CovariantContainer2.class);
}
// Have to have own version as Kotlin inline functions cannot be easily called from Java
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
SerializationOutput ser = new SerializationOutput(factory1);
SerializedBytes<Object> bytes = ser.serialize(container);
DeserializationInput des = new DeserializationInput(factory1);
T deserialized = des.deserialize(bytes, clazz);
Assert.assertEquals(container, deserialized);
}
}

View File

@ -76,54 +76,66 @@ class ConfigParsingTest {
testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true)
}
@Test
fun CordaX500Name() {
testPropertyType<CordaX500NameData, CordaX500NameListData, CordaX500Name>(
CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB"),
CordaX500Name(organisation = "Mock Party 2", locality = "London", country = "GB"),
valuesToString = true)
}
@Test
fun `flat Properties`() {
val config = config("value" to mapOf("key" to "prop"))
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["key"] = "prop" })
val data = PropertiesData(Properties().apply { this["key"] = "prop" })
assertThat(config.parseAs<PropertiesData>()).isEqualTo(data)
assertThat(data.toConfig()).isEqualTo(config)
}
@Test
fun `Properties key with dot`() {
val config = config("value" to mapOf("key.key2" to "prop"))
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["key.key2"] = "prop" })
val data = PropertiesData(Properties().apply { this["key.key2"] = "prop" })
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(data.value)
}
@Test
fun `nested Properties`() {
val config = config("value" to mapOf("first" to mapOf("second" to "prop")))
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["first.second"] = "prop" })
val data = PropertiesData(Properties().apply { this["first.second"] = "prop" })
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(data.value)
assertThat(data.toConfig()).isEqualTo(config)
}
@Test
fun `List of Properties`() {
val config = config("values" to listOf(emptyMap(), mapOf("key" to "prop")))
assertThat(config.parseAs<PropertiesListData>().values).containsExactly(
val data = PropertiesListData(listOf(
Properties(),
Properties().apply { this["key"] = "prop" })
Properties().apply { this["key"] = "prop" }))
assertThat(config.parseAs<PropertiesListData>().values).isEqualTo(data.values)
assertThat(data.toConfig()).isEqualTo(config)
}
@Test
fun `Set`() {
val config = config("values" to listOf("a", "a", "b"))
assertThat(config.parseAs<StringSetData>().values).containsOnly("a", "b")
val data = StringSetData(setOf("a", "b"))
assertThat(config("values" to listOf("a", "a", "b")).parseAs<StringSetData>()).isEqualTo(data)
assertThat(data.toConfig()).isEqualTo(config("values" to listOf("a", "b")))
assertThat(empty().parseAs<StringSetData>().values).isEmpty()
}
@Test
fun x500Name() {
testPropertyType<X500NameData, X500NameListData, CordaX500Name>(CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB"), CordaX500Name(organisation = "Mock Party 2", locality = "London", country = "GB"), valuesToString = true)
}
@Test
fun `multi property data class`() {
val data = config(
val config = config(
"b" to true,
"i" to 123,
"l" to listOf("a", "b"))
.parseAs<MultiPropertyData>()
val data = config.parseAs<MultiPropertyData>()
assertThat(data.i).isEqualTo(123)
assertThat(data.b).isTrue()
assertThat(data.l).containsExactly("a", "b")
assertThat(data.toConfig()).isEqualTo(config)
}
@Test
@ -133,6 +145,7 @@ class ConfigParsingTest {
"value" to "nested"))
val data = NestedData(StringData("nested"))
assertThat(config.parseAs<NestedData>()).isEqualTo(data)
assertThat(data.toConfig()).isEqualTo(config)
}
@Test
@ -143,12 +156,14 @@ class ConfigParsingTest {
mapOf("value" to "2")))
val data = DataListData(listOf(StringData("1"), StringData("2")))
assertThat(config.parseAs<DataListData>()).isEqualTo(data)
assertThat(data.toConfig()).isEqualTo(config)
}
@Test
fun `default value property`() {
assertThat(config("a" to 3).parseAs<DefaultData>()).isEqualTo(DefaultData(3, 2))
assertThat(config("a" to 3, "defaultOfTwo" to 3).parseAs<DefaultData>()).isEqualTo(DefaultData(3, 3))
assertThat(DefaultData(3).toConfig()).isEqualTo(config("a" to 3, "defaultOfTwo" to 2))
}
@Test
@ -156,12 +171,19 @@ class ConfigParsingTest {
assertThat(empty().parseAs<NullableData>().nullable).isNull()
assertThat(config("nullable" to null).parseAs<NullableData>().nullable).isNull()
assertThat(config("nullable" to "not null").parseAs<NullableData>().nullable).isEqualTo("not null")
assertThat(NullableData(null).toConfig()).isEqualTo(empty())
}
@Test
fun `old config property`() {
assertThat(config("oldValue" to "old").parseAs<OldData>().newValue).isEqualTo("old")
assertThat(config("newValue" to "new").parseAs<OldData>().newValue).isEqualTo("new")
assertThat(OldData("old").toConfig()).isEqualTo(config("newValue" to "old"))
}
@Test
fun `static field`() {
assertThat(DataWithCompanion(3).toConfig()).isEqualTo(config("value" to 3))
}
private inline fun <reified S : SingleData<V>, reified L : ListData<V>, V : Any> testPropertyType(
@ -177,6 +199,7 @@ class ConfigParsingTest {
val config = config("value" to if (valueToString) value.toString() else value)
val data = constructor.call(value)
assertThat(config.parseAs<T>().value).isEqualTo(data.value)
assertThat(data.toConfig()).isEqualTo(config)
}
private inline fun <reified T : ListData<V>, V : Any> testListProperty(value1: V, value2: V, valuesToString: Boolean) {
@ -187,6 +210,7 @@ class ConfigParsingTest {
val config = config("values" to configValues.take(n))
val data = constructor.call(rawValues.take(n))
assertThat(config.parseAs<T>().values).isEqualTo(data.values)
assertThat(data.toConfig()).isEqualTo(config)
}
assertThat(empty().parseAs<T>().values).isEmpty()
}
@ -228,8 +252,8 @@ class ConfigParsingTest {
data class PathListData(override val values: List<Path>) : ListData<Path>
data class URLData(override val value: URL) : SingleData<URL>
data class URLListData(override val values: List<URL>) : ListData<URL>
data class X500NameData(override val value: CordaX500Name) : SingleData<CordaX500Name>
data class X500NameListData(override val values: List<CordaX500Name>) : ListData<CordaX500Name>
data class CordaX500NameData(override val value: CordaX500Name) : SingleData<CordaX500Name>
data class CordaX500NameListData(override val values: List<CordaX500Name>) : ListData<CordaX500Name>
data class PropertiesData(override val value: Properties) : SingleData<Properties>
data class PropertiesListData(override val values: List<Properties>) : ListData<Properties>
data class MultiPropertyData(val i: Int, val b: Boolean, val l: List<String>)
@ -240,7 +264,12 @@ class ConfigParsingTest {
data class OldData(
@OldConfig("oldValue")
val newValue: String)
data class DataWithCompanion(val value: Int) {
companion object {
@Suppress("unused")
val companionValue = 2
}
}
enum class TestEnum { Value1, Value2 }
}

View File

@ -9,7 +9,6 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.*
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
@ -37,7 +36,7 @@ class AttachmentsClassLoaderStaticContractTests : TestDependencyInjectionBase()
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary)
.withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
.withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
}
}
@ -45,12 +44,7 @@ class AttachmentsClassLoaderStaticContractTests : TestDependencyInjectionBase()
@Before
fun `create service hub`() {
serviceHub = MockServices(cordappPackages=listOf("net.corda.nodeapi.internal"))
}
@After
fun `clear packages`() {
unsetCordappPackages()
serviceHub = MockServices(cordappPackages = listOf("net.corda.nodeapi.internal"))
}
@Test

View File

@ -5,6 +5,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.declaredField
import net.corda.core.internal.toWireTransaction
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
@ -44,6 +45,7 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
whenever(serviceHub.attachments).thenReturn(attachmentStorage)
return this.withServiceHub(serviceHub)
}
private fun SerializationContext.withServiceHub(serviceHub: ServiceHub): SerializationContext {
return this.withTokenContext(SerializeAsTokenContextImpl(serviceHub) {}).withProperty(attachmentsClassLoaderEnabledPropertyName, true)
}
@ -53,8 +55,7 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
class DummyServiceHub : MockServices() {
override val cordappProvider: CordappProviderImpl
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH))).start(attachments)
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
private val cordapp get() = cordappProvider.cordapps.first()
val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
val appContext get() = cordappProvider.getAppContext(cordapp)
@ -265,7 +266,7 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `test serialization of sub-sequence OpaqueBytes`() {
val bytesSequence = ByteSequence.of("0123456789".toByteArray(), 3 ,2)
val bytesSequence = ByteSequence.of("0123456789".toByteArray(), 3, 2)
val bytes = bytesSequence.serialize()
val copiedBytesSequence = bytes.deserialize()
@ -300,7 +301,7 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
kryoSpecific<AttachmentsClassLoaderTests>("Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not") {
kryoSpecific("Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not") {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
@ -309,8 +310,8 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val attachmentRef = serviceHub.attachmentId
val bytes = run {
val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub)
.withClassLoader(child)
.withServiceHub(serviceHub)
.withClassLoader(child)
val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext)
wireTransaction.serialize(context = outboundContext)
}

View File

@ -34,6 +34,23 @@ enum class Foo {
abstract val value: Int
}
enum class BadFood {
Mud {
override val value = -1
};
abstract val value: Int
}
@CordaSerializable
enum class Simple {
Easy
}
enum class BadSimple {
Nasty
}
@CordaSerializable
open class Element
@ -106,17 +123,36 @@ class CordaClassResolverTests {
@Test
fun `Annotation on enum works for specialised entries`() {
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
@Suppress("UNSUPPORTED_FEATURE")
CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java)
}
@Test(expected = KryoException::class)
fun `Unannotated specialised enum does not work`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(BadFood.Mud::class.java)
}
@Test
fun `Annotation on simple enum works`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Simple.Easy::class.java)
}
@Test(expected = KryoException::class)
fun `Unannotated simple enum does not work`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(BadSimple.Nasty::class.java)
}
@Test
fun `Annotation on array element works`() {
val values = arrayOf(Element())
CordaClassResolver(emptyWhitelistContext).getRegistration(values.javaClass)
}
@Test(expected = KryoException::class)
fun `Unannotated array elements do not work`() {
val values = arrayOf(NotSerializable())
CordaClassResolver(emptyWhitelistContext).getRegistration(values.javaClass)
}
@Test
fun `Annotation not needed on abstract class`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(AbstractClass::class.java)
@ -271,6 +307,7 @@ class CordaClassResolverTests {
}
open class SubHashSet<E> : HashSet<E>()
@Test
fun `Check blacklisted subclass`() {
expectedEx.expect(IllegalStateException::class.java)
@ -281,6 +318,7 @@ class CordaClassResolverTests {
}
class SubSubHashSet<E> : SubHashSet<E>()
@Test
fun `Check blacklisted subsubclass`() {
expectedEx.expect(IllegalStateException::class.java)
@ -291,6 +329,7 @@ class CordaClassResolverTests {
}
class ConnectionImpl(private val connection: Connection) : Connection by connection
@Test
fun `Check blacklisted interface impl`() {
expectedEx.expect(IllegalStateException::class.java)
@ -302,6 +341,7 @@ class CordaClassResolverTests {
interface SubConnection : Connection
class SubConnectionImpl(private val subConnection: SubConnection) : SubConnection by subConnection
@Test
fun `Check blacklisted super-interface impl`() {
expectedEx.expect(IllegalStateException::class.java)
@ -320,6 +360,7 @@ class CordaClassResolverTests {
@CordaSerializable
class CordaSerializableHashSet<E> : HashSet<E>()
@Test
fun `Check blacklist precedes CordaSerializable`() {
expectedEx.expect(IllegalStateException::class.java)

View File

@ -43,7 +43,7 @@ class KryoTests : TestDependencyInjectionBase() {
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
SerializationContext.UseCase.Storage)
}
@Test
@ -188,10 +188,10 @@ class KryoTests : TestDependencyInjectionBase() {
@Test
fun `serialize - deserialize PrivacySalt`() {
val expected = PrivacySalt(byteArrayOf(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32
))
val serializedBytes = expected.serialize(factory, context)
val actual = serializedBytes.deserialize(factory, context)
@ -267,7 +267,7 @@ class KryoTests : TestDependencyInjectionBase() {
assertEquals(exception.message, exception2.message)
assertEquals(1, exception2.suppressed.size)
assertNotNull({ exception2.suppressed.find { it.message == toBeSuppressedOnSenderSide.message }})
assertNotNull({ exception2.suppressed.find { it.message == toBeSuppressedOnSenderSide.message } })
val toBeSuppressedOnReceiverSide = IllegalStateException("bazz2")
exception2.addSuppressed(toBeSuppressedOnReceiverSide)

View File

@ -4,19 +4,31 @@ import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.*
import net.corda.node.services.statemachine.SessionData
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import net.corda.nodeapi.internal.serialization.amqp.Envelope
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.amqpSpecific
import net.corda.testing.kryoSpecific
import org.assertj.core.api.Assertions
import org.junit.Assert.*
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
import java.nio.charset.StandardCharsets.*
import java.nio.charset.StandardCharsets.US_ASCII
import java.util.*
class ListsSerializationTest : TestDependencyInjectionBase() {
private companion object {
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
fun <T : Any> verifyEnvelope(serBytes: SerializedBytes<T>, envVerBody: (Envelope) -> Unit) =
amqpSpecific("AMQP specific envelope verification") {
val context = SerializationFactory.defaultFactory.defaultContext
val envelope = DeserializationInput(SerializerFactory(context.whitelist, context.deserializationClassLoader)).getEnvelope(serBytes)
envVerBody(envelope)
}
}
@Test
@ -29,21 +41,24 @@ class ListsSerializationTest : TestDependencyInjectionBase() {
@Test
fun `check list can be serialized as part of SessionData`() {
run {
val sessionData = SessionData(123, listOf(1))
val sessionData = SessionData(123, listOf(1).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(listOf(1), sessionData.payload.deserialize())
}
run {
val sessionData = SessionData(123, listOf(1, 2))
val sessionData = SessionData(123, listOf(1, 2).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(listOf(1, 2), sessionData.payload.deserialize())
}
run {
val sessionData = SessionData(123, emptyList<Int>())
val sessionData = SessionData(123, emptyList<Int>().serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(emptyList<Int>(), sessionData.payload.deserialize())
}
}
@Test
fun `check empty list serialises as Java emptyList`() {
fun `check empty list serialises as Java emptyList`() = kryoSpecific("Kryo specific test") {
val nameID = 0
val serializedForm = emptyList<Int>().serialize()
val output = ByteArrayOutputStream().apply {
@ -60,7 +75,7 @@ class ListsSerializationTest : TestDependencyInjectionBase() {
data class WrongPayloadType(val payload: ArrayList<Int>)
@Test
fun `check throws for forbidden declared type`() = amqpSpecific<ListsSerializationTest>("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 = ArrayList<Int>()
payload.add(1)
payload.add(2)
@ -68,11 +83,34 @@ class ListsSerializationTest : TestDependencyInjectionBase() {
Assertions.assertThatThrownBy { wrongPayloadType.serialize() }
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive collection type for declaredType")
}
@CordaSerializable
interface Parent
data class Child(val value: Int) : Parent
@CordaSerializable
data class CovariantContainer<out T : Parent>(val payload: List<T>)
@Test
fun `check covariance`() {
val payload = ArrayList<Child>()
payload.add(Child(1))
payload.add(Child(2))
val container = CovariantContainer(payload)
fun verifyEnvelopeBody(envelope: Envelope) {
envelope.schema.types.single { typeNotation -> typeNotation.name == java.util.List::class.java.name + "<?>" }
}
assertEqualAfterRoundTripSerialization(container, { bytes -> verifyEnvelope(bytes, ::verifyEnvelopeBody) })
}
}
internal inline fun<reified T : Any> assertEqualAfterRoundTripSerialization(obj: T) {
internal inline fun <reified T : Any> assertEqualAfterRoundTripSerialization(obj: T, noinline streamValidation: ((SerializedBytes<T>) -> Unit)? = null) {
val serializedForm: SerializedBytes<T> = obj.serialize()
streamValidation?.invoke(serializedForm)
val deserializedInstance = serializedForm.deserialize()
assertEquals(obj, deserializedInstance)

View File

@ -3,17 +3,19 @@ package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.amqpSpecific
import net.corda.testing.kryoSpecific
import org.assertj.core.api.Assertions
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import org.bouncycastle.asn1.x500.X500Name
import java.io.ByteArrayOutputStream
import java.util.*
import kotlin.test.assertEquals
class MapsSerializationTest : TestDependencyInjectionBase() {
private companion object {
@ -22,7 +24,7 @@ class MapsSerializationTest : TestDependencyInjectionBase() {
}
@Test
fun `check EmptyMap serialization`() = amqpSpecific<MapsSerializationTest>("kotlin.collections.EmptyMap is not enabled for Kryo serialization") {
fun `check EmptyMap serialization`() = amqpSpecific("kotlin.collections.EmptyMap is not enabled for Kryo serialization") {
assertEqualAfterRoundTripSerialization(emptyMap<Any, Any>())
}
@ -33,15 +35,16 @@ class MapsSerializationTest : TestDependencyInjectionBase() {
@Test
fun `check list can be serialized as part of SessionData`() {
val sessionData = SessionData(123, smallMap)
val sessionData = SessionData(123, smallMap.serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(smallMap, sessionData.payload.deserialize())
}
@CordaSerializable
data class WrongPayloadType(val payload: HashMap<String, String>)
@Test
fun `check throws for forbidden declared type`() = amqpSpecific<ListsSerializationTest>("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 wrongPayloadType = WrongPayloadType(payload)
Assertions.assertThatThrownBy { wrongPayloadType.serialize() }
@ -64,7 +67,7 @@ class MapsSerializationTest : TestDependencyInjectionBase() {
}
@Test
fun `check empty map serialises as Java emptyMap`() = kryoSpecific<MapsSerializationTest>("Specifically checks Kryo serialization") {
fun `check empty map serialises as Java emptyMap`() = kryoSpecific("Specifically checks Kryo serialization") {
val nameID = 0
val serializedForm = emptyMap<Int, Int>().serialize()
val output = ByteArrayOutputStream().apply {

View File

@ -0,0 +1,42 @@
package net.corda.nodeapi.internal.serialization
import net.corda.core.crypto.Crypto
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.testing.TestDependencyInjectionBase
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.security.PrivateKey
import kotlin.test.assertTrue
@RunWith(Parameterized::class)
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) : TestDependencyInjectionBase() {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> {
val privateKeys: List<PrivateKey> = Crypto.supportedSignatureSchemes().filterNot { Crypto.COMPOSITE_KEY === it }
.map { Crypto.generateKeyPair(it).private }
return privateKeys.map { arrayOf<Any>(it, PrivateKeySerializationTest::class.java.simpleName + "-" + it.javaClass.simpleName) }
}
}
@Test
fun `passed with expected UseCases`() {
assertTrue { privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes.isNotEmpty() }
assertTrue { privateKey.serialize(context = SerializationDefaults.CHECKPOINT_CONTEXT).bytes.isNotEmpty() }
}
@Test
fun `failed with wrong UseCase`() {
assertThatThrownBy { privateKey.serialize(context = SerializationDefaults.P2P_CONTEXT) }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("UseCase '${P2P}' is not within")
}
}

View File

@ -12,7 +12,7 @@ import org.junit.Before
import org.junit.Test
import java.io.ByteArrayOutputStream
class SerializationTokenTest : TestDependencyInjectionBase() {
class SerializationTokenTest : TestDependencyInjectionBase() {
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@ -58,7 +58,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() {
val testContext = this.context.withTokenContext(context)
val serializedBytes = tokenizableBefore.serialize(factory, testContext)
val tokenizableAfter = serializedBytes.deserialize(factory, testContext)
assertThat(tokenizableAfter).isSameAs(tokenizableBefore)
assertThat(tokenizableAfter).isSameAs(tokenizableBefore)
}
@Test(expected = UnsupportedOperationException::class)
@ -92,11 +92,11 @@ class SerializationTokenTest : TestDependencyInjectionBase() {
val kryo: Kryo = DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(this.context)))
val stream = ByteArrayOutputStream()
Output(stream).use {
it.write(KryoHeaderV0_1.bytes)
kryo.writeClass(it, SingletonSerializeAsToken::class.java)
kryo.writeObject(it, emptyList<Any>())
}
Output(stream).use {
it.write(KryoHeaderV0_1.bytes)
kryo.writeClass(it, SingletonSerializeAsToken::class.java)
kryo.writeObject(it, emptyList<Any>())
}
val serializedBytes = SerializedBytes<Any>(stream.toByteArray())
serializedBytes.deserialize(factory, testContext)
}
@ -105,6 +105,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() {
object UnitSerializationToken : SerializationToken {
override fun fromToken(context: SerializeAsTokenContext): Any = UnitSerializeAsToken()
}
override fun toToken(context: SerializeAsTokenContext): SerializationToken = UnitSerializationToken
}

View File

@ -2,10 +2,13 @@ package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.testing.TestDependencyInjectionBase
import org.junit.Assert.*
import net.corda.testing.kryoSpecific
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.util.*
@ -25,21 +28,24 @@ class SetsSerializationTest : TestDependencyInjectionBase() {
@Test
fun `check set can be serialized as part of SessionData`() {
run {
val sessionData = SessionData(123, setOf(1))
val sessionData = SessionData(123, setOf(1).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(setOf(1), sessionData.payload.deserialize())
}
run {
val sessionData = SessionData(123, setOf(1, 2))
val sessionData = SessionData(123, setOf(1, 2).serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(setOf(1, 2), sessionData.payload.deserialize())
}
run {
val sessionData = SessionData(123, emptySet<Int>())
val sessionData = SessionData(123, emptySet<Int>().serialize())
assertEqualAfterRoundTripSerialization(sessionData)
assertEquals(emptySet<Int>(), sessionData.payload.deserialize())
}
}
@Test
fun `check empty set serialises as Java emptySet`() {
fun `check empty set serialises as Java emptySet`() = kryoSpecific("Checks Kryo header properties") {
val nameID = 0
val serializedForm = emptySet<Int>().serialize()
val output = ByteArrayOutputStream().apply {

View File

@ -17,25 +17,28 @@ class DeserializeMapTests {
@Test
fun mapTest() {
data class C(val c: Map<String, Int>)
val c = C (mapOf("A" to 1, "B" to 2))
val c = C(mapOf("A" to 1, "B" to 2))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected=java.io.NotSerializableException::class)
@Test(expected = java.io.NotSerializableException::class)
fun abstractMapFromMapOf() {
data class C(val c: AbstractMap<String, Int>)
val c = C (mapOf("A" to 1, "B" to 2) as AbstractMap)
val c = C(mapOf("A" to 1, "B" to 2) as AbstractMap)
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@Test(expected=java.io.NotSerializableException::class)
@Test(expected = java.io.NotSerializableException::class)
fun abstractMapFromTreeMap() {
data class C(val c: AbstractMap<String, Int>)
val c = C (TreeMap(mapOf("A" to 1, "B" to 2)))
val c = C(TreeMap(mapOf("A" to 1, "B" to 2)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
@ -44,7 +47,8 @@ class DeserializeMapTests {
@Test
fun sortedMapTest() {
data class C(val c: SortedMap<String, Int>)
val c = C(sortedMapOf ("A" to 1, "B" to 2))
val c = C(sortedMapOf("A" to 1, "B" to 2))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
}
@ -52,7 +56,8 @@ class DeserializeMapTests {
@Test
fun navigableMapTest() {
data class C(val c: NavigableMap<String, Int>)
val c = C(TreeMap (mapOf("A" to 1, "B" to 2)).descendingMap())
val c = C(TreeMap(mapOf("A" to 1, "B" to 2)).descendingMap())
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
@ -61,9 +66,10 @@ class DeserializeMapTests {
@Test
fun dictionaryTest() {
data class C(val c: Dictionary<String, Int>)
val v : Hashtable<String, Int> = Hashtable()
v.put ("a", 1)
v.put ("b", 2)
val v: Hashtable<String, Int> = Hashtable()
v.put("a", 1)
v.put("b", 2)
val c = C(v)
// expected to throw
@ -74,9 +80,10 @@ class DeserializeMapTests {
@Test
fun hashtableTest() {
data class C(val c: Hashtable<String, Int>)
val v : Hashtable<String, Int> = Hashtable()
v.put ("a", 1)
v.put ("b", 2)
val v: Hashtable<String, Int> = Hashtable()
v.put("a", 1)
v.put("b", 2)
val c = C(v)
// expected to throw
@ -86,8 +93,9 @@ class DeserializeMapTests {
@Test
fun hashMapTest() {
data class C(val c : HashMap<String, Int>)
val c = C (HashMap (mapOf("A" to 1, "B" to 2)))
data class C(val c: HashMap<String, Int>)
val c = C(HashMap(mapOf("A" to 1, "B" to 2)))
// expect this to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
@ -96,8 +104,9 @@ class DeserializeMapTests {
@Test
fun weakHashMapTest() {
data class C(val c : WeakHashMap<String, Int>)
val c = C (WeakHashMap (mapOf("A" to 1, "B" to 2)))
data class C(val c: WeakHashMap<String, Int>)
val c = C(WeakHashMap(mapOf("A" to 1, "B" to 2)))
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Weak references with map types not supported. Suggested fix: use java.util.LinkedHashMap instead.")
@ -106,7 +115,8 @@ class DeserializeMapTests {
@Test
fun concreteTreeMapTest() {
data class C(val c: TreeMap<String, Int>)
val c = C(TreeMap (mapOf("A" to 1, "B" to 3)))
val c = C(TreeMap(mapOf("A" to 1, "B" to 3)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)
@ -114,8 +124,9 @@ class DeserializeMapTests {
@Test
fun concreteLinkedHashMapTest() {
data class C(val c : LinkedHashMap<String, Int>)
val c = C (LinkedHashMap (mapOf("A" to 1, "B" to 2)))
data class C(val c: LinkedHashMap<String, Int>)
val c = C(LinkedHashMap(mapOf("A" to 1, "B" to 2)))
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c)
DeserializationInput(sf).deserialize(serialisedBytes)

View File

@ -402,7 +402,8 @@ class DeserializeSimpleTypesTests {
@Test
fun arrayOfArrayOfInt() {
class C(val c: Array<Array<Int>>)
val c = C (arrayOf (arrayOf(1,2,3), arrayOf(4,5,6)))
val c = C(arrayOf(arrayOf(1, 2, 3), arrayOf(4, 5, 6)))
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
@ -421,7 +422,8 @@ class DeserializeSimpleTypesTests {
@Test
fun arrayOfIntArray() {
class C(val c: Array<IntArray>)
val c = C (arrayOf (IntArray(3), IntArray(3)))
val c = C(arrayOf(IntArray(3), IntArray(3)))
c.c[0][0] = 1; c.c[0][1] = 2; c.c[0][2] = 3
c.c[1][0] = 4; c.c[1][1] = 5; c.c[1][2] = 6
@ -444,22 +446,32 @@ class DeserializeSimpleTypesTests {
class C(val c: Array<Array<IntArray>>)
val c = C(arrayOf(arrayOf(IntArray(3), IntArray(3), IntArray(3)),
arrayOf(IntArray(3), IntArray(3), IntArray(3)),
arrayOf(IntArray(3), IntArray(3), IntArray(3))))
arrayOf(IntArray(3), IntArray(3), IntArray(3)),
arrayOf(IntArray(3), IntArray(3), IntArray(3))))
for (i in 0..2) { for (j in 0..2) { for (k in 0..2) { c.c[i][j][k] = i + j + k } } }
for (i in 0..2) {
for (j in 0..2) {
for (k in 0..2) {
c.c[i][j][k] = i + j + k
}
}
}
val serialisedC = TestSerializationOutput(VERBOSE, sf1).serialize(c)
val deserializedC = DeserializationInput(sf1).deserialize(serialisedC)
for (i in 0..2) { for (j in 0..2) { for (k in 0..2) {
assertEquals(c.c[i][j][k], deserializedC.c[i][j][k])
}}}
for (i in 0..2) {
for (j in 0..2) {
for (k in 0..2) {
assertEquals(c.c[i][j][k], deserializedC.c[i][j][k])
}
}
}
}
@Test
fun nestedRepeatedTypes() {
class A(val a : A?, val b: Int)
class A(val a: A?, val b: Int)
var a = A(A(A(A(A(null, 1), 2), 3), 4), 5)

View File

@ -1,5 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import org.junit.Test
import java.time.DayOfWeek
@ -10,12 +12,18 @@ import java.io.File
import java.io.NotSerializableException
import net.corda.core.serialization.SerializedBytes
import org.assertj.core.api.Assertions
class EnumTests {
enum class Bras {
TSHIRT, UNDERWIRE, PUSHUP, BRALETTE, STRAPLESS, SPORTS, BACKLESS, PADDED
}
@CordaSerializable
enum class AnnotatedBras {
TSHIRT, UNDERWIRE, PUSHUP, BRALETTE, STRAPLESS, SPORTS, BACKLESS, PADDED
}
// The state of the OldBras enum when the tests in changedEnum1 were serialised
// - use if the test file needs regenerating
//enum class OldBras {
@ -41,7 +49,7 @@ class EnumTests {
}
enum class BrasWithInit (val someList: List<Int>) {
enum class BrasWithInit(val someList: List<Int>) {
TSHIRT(emptyList()),
UNDERWIRE(listOf(1, 2, 3)),
PUSHUP(listOf(100, 200)),
@ -82,7 +90,7 @@ class EnumTests {
assertEquals(8, schema_bras.choices.size)
Bras.values().forEach {
val bra = it
assertNotNull (schema_bras.choices.find { it.name == bra.name })
assertNotNull(schema_bras.choices.find { it.name == bra.name })
}
}
@ -107,7 +115,7 @@ class EnumTests {
assertEquals(8, schema_bras.choices.size)
Bras.values().forEach {
val bra = it
assertNotNull (schema_bras.choices.find { it.name == bra.name })
assertNotNull(schema_bras.choices.find { it.name == bra.name })
}
// Test the actual deserialised object
@ -116,13 +124,13 @@ class EnumTests {
@Test
fun multiEnum() {
data class Support (val top: Bras, val day : DayOfWeek)
data class WeeklySupport (val tops: List<Support>)
data class Support(val top: Bras, val day: DayOfWeek)
data class WeeklySupport(val tops: List<Support>)
val week = WeeklySupport (listOf(
Support (Bras.PUSHUP, DayOfWeek.MONDAY),
Support (Bras.UNDERWIRE, DayOfWeek.WEDNESDAY),
Support (Bras.PADDED, DayOfWeek.SUNDAY)))
val week = WeeklySupport(listOf(
Support(Bras.PUSHUP, DayOfWeek.MONDAY),
Support(Bras.UNDERWIRE, DayOfWeek.WEDNESDAY),
Support(Bras.PADDED, DayOfWeek.SUNDAY)))
val obj = DeserializationInput(sf1).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(week))
@ -138,7 +146,7 @@ class EnumTests {
fun enumWithInit() {
data class C(val c: BrasWithInit)
val c = C (BrasWithInit.PUSHUP)
val c = C(BrasWithInit.PUSHUP)
val obj = DeserializationInput(sf1).deserialize(TestSerializationOutput(VERBOSE, sf1).serialize(c))
assertEquals(c.c, obj.c)
@ -149,7 +157,7 @@ class EnumTests {
val path = EnumTests::class.java.getResource("EnumTests.changedEnum1")
val f = File(path.toURI())
data class C (val a: OldBras)
data class C(val a: OldBras)
// Original version of the class for the serialised version of this class
//
@ -169,7 +177,7 @@ class EnumTests {
val path = EnumTests::class.java.getResource("EnumTests.changedEnum2")
val f = File(path.toURI())
data class C (val a: OldBras2)
data class C(val a: OldBras2)
// DO NOT CHANGE THIS, it's important we serialise with a value that doesn't
// change position in the upated enum class
@ -186,4 +194,74 @@ class EnumTests {
// we expect this to throw
DeserializationInput(sf1).deserialize(SerializedBytes<C>(sc2))
}
@Test
fun enumNotWhitelistedFails() {
data class C(val c: Bras)
class WL(val allowed: String) : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean {
return type.name == allowed
}
}
val factory = SerializerFactory(WL(classTestName("C")), ClassLoader.getSystemClassLoader())
Assertions.assertThatThrownBy {
TestSerializationOutput(VERBOSE, factory).serialize(C(Bras.UNDERWIRE))
}.isInstanceOf(NotSerializableException::class.java)
}
@Test
fun enumWhitelisted() {
data class C(val c: Bras)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean {
return type.name == "net.corda.nodeapi.internal.serialization.amqp.EnumTests\$enumWhitelisted\$C" ||
type.name == "net.corda.nodeapi.internal.serialization.amqp.EnumTests\$Bras"
}
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
// if it all works, this won't explode
TestSerializationOutput(VERBOSE, factory).serialize(C(Bras.UNDERWIRE))
}
@Test
fun enumAnnotated() {
@CordaSerializable data class C(val c: AnnotatedBras)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = false
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
// if it all works, this won't explode
TestSerializationOutput(VERBOSE, factory).serialize(C(AnnotatedBras.UNDERWIRE))
}
@Test
fun deserializeNonWhitlistedEnum() {
data class C(val c: Bras)
class WL(val allowed: List<String>) : ClassWhitelist {
override fun hasListed(type: Class<*>) = type.name in allowed
}
// first serialise the class using a context in which Bras are whitelisted
val factory = SerializerFactory(WL(listOf(classTestName("C"),
"net.corda.nodeapi.internal.serialization.amqp.EnumTests\$Bras")),
ClassLoader.getSystemClassLoader())
val bytes = TestSerializationOutput(VERBOSE, factory).serialize(C(Bras.UNDERWIRE))
// then take that serialised object and attempt to deserialize it in a context that
// disallows the Bras enum
val factory2 = SerializerFactory(WL(listOf(classTestName("C"))), ClassLoader.getSystemClassLoader())
Assertions.assertThatThrownBy {
DeserializationInput(factory2).deserialize(bytes)
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -32,7 +32,7 @@ class EvolvabilityTests {
// f.writeBytes(sc.bytes)
// new version of the class, in this case the order of the parameters has been swapped
data class C (val b: Int, val a: Int)
data class C(val b: Int, val a: Int)
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
@ -56,7 +56,7 @@ class EvolvabilityTests {
// f.writeBytes(sc.bytes)
// new version of the class, in this case the order of the parameters has been swapped
data class C (val b: String, val a: Int)
data class C(val b: String, val a: Int)
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
@ -79,13 +79,13 @@ class EvolvabilityTests {
// f.writeBytes(sc.bytes)
// println ("Path = $path")
data class C (val a: Int, val b: Int?)
data class C(val a: Int, val b: Int?)
val sc2 = f.readBytes()
val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes<C>(sc2))
assertEquals (A, deserializedC.a)
assertEquals (null, deserializedC.b)
assertEquals(A, deserializedC.a)
assertEquals(null, deserializedC.b)
}
@Test(expected = NotSerializableException::class)
@ -104,7 +104,7 @@ class EvolvabilityTests {
// println ("Path = $path")
// new version of the class, in this case a new parameter has been added (b)
data class C (val a: Int, val b: Int)
data class C(val a: Int, val b: Int)
val sc2 = f.readBytes()
@ -132,13 +132,13 @@ class EvolvabilityTests {
// f.writeBytes(scc.bytes)
// println ("Path = $path")
data class CC (val b: String, val d: Int)
data class CC(val b: String, val d: Int)
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (B, deserializedCC.b)
assertEquals (D, deserializedCC.d)
assertEquals(B, deserializedCC.b)
assertEquals(D, deserializedCC.d)
}
@Suppress("UNUSED_VARIABLE")
@ -185,16 +185,16 @@ class EvolvabilityTests {
// println ("Path = $path")
@Suppress("UNUSED")
data class CC (val a: Int, val b: String) {
data class CC(val a: Int, val b: String) {
@DeprecatedConstructorForDeserialization(1)
constructor (a: Int) : this (a, "hello")
constructor (a: Int) : this(a, "hello")
}
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (A, deserializedCC.a)
assertEquals ("hello", deserializedCC.b)
assertEquals(A, deserializedCC.a)
assertEquals("hello", deserializedCC.b)
}
@Test(expected = NotSerializableException::class)
@ -214,9 +214,9 @@ class EvolvabilityTests {
// f.writeBytes(scc.bytes)
// println ("Path = $path")
data class CC (val a: Int, val b: String) {
data class CC(val a: Int, val b: String) {
// constructor annotation purposefully omitted
constructor (a: Int) : this (a, "hello")
constructor (a: Int) : this(a, "hello")
}
// we expect this to throw as we should not find any constructors
@ -242,20 +242,20 @@ class EvolvabilityTests {
// println ("Path = $path")
@Suppress("UNUSED")
data class CC (val a: Int, val b: Int, val c: String, val d: String) {
data class CC(val a: Int, val b: Int, val c: String, val d: String) {
// ensure none of the original parameters align with the initial
// construction order
@DeprecatedConstructorForDeserialization(1)
constructor (c: String, a: Int, b: Int) : this (a, b, c, "wibble")
constructor (c: String, a: Int, b: Int) : this(a, b, c, "wibble")
}
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (A, deserializedCC.a)
assertEquals (B, deserializedCC.b)
assertEquals (C, deserializedCC.c)
assertEquals ("wibble", deserializedCC.d)
assertEquals(A, deserializedCC.a)
assertEquals(B, deserializedCC.b)
assertEquals(C, deserializedCC.c)
assertEquals("wibble", deserializedCC.d)
}
@Test
@ -277,20 +277,20 @@ class EvolvabilityTests {
// println ("Path = $path")
// b is removed, d is added
data class CC (val a: Int, val c: String, val d: String) {
data class CC(val a: Int, val c: String, val d: String) {
// ensure none of the original parameters align with the initial
// construction order
@Suppress("UNUSED")
@DeprecatedConstructorForDeserialization(1)
constructor (c: String, a: Int) : this (a, c, "wibble")
constructor (c: String, a: Int) : this(a, c, "wibble")
}
val sc2 = f.readBytes()
val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes<CC>(sc2))
assertEquals (A, deserializedCC.a)
assertEquals (C, deserializedCC.c)
assertEquals ("wibble", deserializedCC.d)
assertEquals(A, deserializedCC.a)
assertEquals(C, deserializedCC.c)
assertEquals("wibble", deserializedCC.d)
}
@Test
@ -322,13 +322,15 @@ class EvolvabilityTests {
// println ("Path = $path1")
@Suppress("UNUSED")
data class C (val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
@DeprecatedConstructorForDeserialization(1)
constructor (b: Int, a: Int) : this (-1, -1, b, a, -1)
constructor (b: Int, a: Int) : this(-1, -1, b, a, -1)
@DeprecatedConstructorForDeserialization(2)
constructor (a: Int, c: Int, b: Int) : this (-1, c, b, a, -1)
constructor (a: Int, c: Int, b: Int) : this(-1, c, b, a, -1)
@DeprecatedConstructorForDeserialization(3)
constructor (a: Int, b: Int, c: Int, d: Int) : this (-1, c, b, a, d)
constructor (a: Int, b: Int, c: Int, d: Int) : this(-1, c, b, a, d)
}
val sb1 = File(path1.toURI()).readBytes()
@ -376,15 +378,16 @@ class EvolvabilityTests {
// println ("Path = $path")
// Add a parameter to inner but keep outer unchanged
data class Inner (val a: Int, val b: String?)
data class Outer (val a: Int, val b: Inner)
data class Inner(val a: Int, val b: String?)
data class Outer(val a: Int, val b: Inner)
val sc2 = f.readBytes()
val outer = DeserializationInput(sf).deserialize(SerializedBytes<Outer>(sc2))
assertEquals (oa, outer.a)
assertEquals (ia, outer.b.a)
assertEquals (null, outer.b.b)
assertEquals(oa, outer.a)
assertEquals(ia, outer.b.a)
assertEquals(null, outer.b.b)
}
@Test
@ -416,15 +419,18 @@ class EvolvabilityTests {
// println ("Path = $path1")
@Suppress("UNUSED")
data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
@DeprecatedConstructorForDeserialization(1)
constructor (b: Int, c: Int) : this (b, c, -1, -1, -1, -1)
constructor (b: Int, c: Int) : this(b, c, -1, -1, -1, -1)
@DeprecatedConstructorForDeserialization(2)
constructor (b: Int, c: Int, d: Int) : this (b, c, d, -1, -1, -1)
constructor (b: Int, c: Int, d: Int) : this(b, c, d, -1, -1, -1)
@DeprecatedConstructorForDeserialization(3)
constructor (b: Int, c: Int, d: Int, e: Int) : this (b, c, d, e, -1, -1)
constructor (b: Int, c: Int, d: Int, e: Int) : this(b, c, d, e, -1, -1)
@DeprecatedConstructorForDeserialization(4)
constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this (b, c, d, e, f, -1)
constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this(b, c, d, e, f, -1)
}
val sb1 = File(path1.toURI()).readBytes()

View File

@ -50,7 +50,7 @@ class SerializationOutputTests {
data class testShort(val s: Short)
data class testBoolean(val b : Boolean)
data class testBoolean(val b: Boolean)
interface FooInterface {
val pub: Int
@ -145,13 +145,13 @@ class SerializationOutputTests {
data class PolymorphicProperty(val foo: FooInterface?)
private inline fun<reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory (
AllWhitelist, ClassLoader.getSystemClassLoader()),
freshDeserializationFactory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): T {
private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
freshDeserializationFactory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): T {
val ser = SerializationOutput(factory)
val bytes = ser.serialize(obj)
@ -446,10 +446,10 @@ class SerializationOutputTests {
try {
try {
throw IOException("Layer 1")
} catch(t: Throwable) {
} catch (t: Throwable) {
throw IllegalStateException("Layer 2", t)
}
} catch(t: Throwable) {
} catch (t: Throwable) {
val desThrowable = serdesThrowableWithInternalInfo(t, factory, factory2, false)
assertSerializedThrowableEquivalent(t, desThrowable)
}
@ -476,12 +476,12 @@ class SerializationOutputTests {
try {
try {
throw IOException("Layer 1")
} catch(t: Throwable) {
} catch (t: Throwable) {
val e = IllegalStateException("Layer 2")
e.addSuppressed(t)
throw e
}
} catch(t: Throwable) {
} catch (t: Throwable) {
val desThrowable = serdesThrowableWithInternalInfo(t, factory, factory2, false)
assertSerializedThrowableEquivalent(t, desThrowable)
}
@ -534,7 +534,22 @@ class SerializationOutputTests {
}
}
val FOO_PROGRAM_ID = "net.corda.nodeapi.internal.serialization.amqp.SerializationOutputTests.FooContract"
@Test
fun `test custom object`() {
serdes(FooContract)
}
@Test
@Ignore("Cannot serialize due to known Kotlin/serialization limitation")
fun `test custom anonymous object`() {
val anonymous: Contract = object : Contract {
override fun verify(tx: LedgerTransaction) {
}
}
serdes(anonymous)
}
private val FOO_PROGRAM_ID = "net.corda.nodeapi.internal.serialization.amqp.SerializationOutputTests.FooContract"
class FooState : ContractState {
override val participants: List<AbstractParty> = emptyList()
}

View File

@ -19,6 +19,7 @@ class SerializeAndReturnSchemaTest {
@Test
fun getSchema() {
data class C(val a: Int, val b: Int)
val a = 1
val b = 2

View File

@ -0,0 +1,144 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializedBytes
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.carpenter.ClassCarpenter
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.lang.reflect.Type
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals
class InStatic : Exception("Help!, help!, I'm being repressed")
class C {
companion object {
init {
throw InStatic()
}
}
}
// To re-setup the resource file for the tests
// * deserializeTest
// * deserializeTest2
// comment out the companion object from here, comment out the test code and uncomment
// the generation code, then re-run the test and copy the file shown in the output print
// to the resource directory
class C2(var b: Int) {
/*
companion object {
init {
throw InStatic()
}
}
*/
}
class StaticInitialisationOfSerializedObjectTest {
@Test(expected = java.lang.ExceptionInInitializerError::class)
fun itBlowsUp() {
C()
}
@Test
fun KotlinObjectWithCompanionObject() {
data class D(val c: C)
val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
val typeMap = sf::class.java.getDeclaredField("serializersByType")
typeMap.isAccessible = true
@Suppress("UNCHECKED_CAST")
val serialisersByType = typeMap.get(sf) as ConcurrentHashMap<Type, AMQPSerializer<Any>>
// pre building a serializer, we shouldn't have anything registered
assertEquals(0, serialisersByType.size)
// build a serializer for type D without an instance of it to serialise, since
// we can't actually construct one
sf.get(null, D::class.java)
// post creation of the serializer we should have one element in the map, this
// proves we didn't statically construct an instance of C when building the serializer
assertEquals(1, serialisersByType.size)
}
@Test
fun deserializeTest() {
data class D(val c: C2)
val path = EvolvabilityTests::class.java.getResource("StaticInitialisationOfSerializedObjectTest.deserializeTest")
val f = File(path.toURI())
// Original version of the class for the serialised version of this class
//
//val sf1 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
//val sc = SerializationOutput(sf1).serialize(D(C2(20)))
//f.writeBytes(sc.bytes)
//println (path)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) =
type.name == "net.corda.nodeapi.internal.serialization.amqp" +
".StaticInitialisationOfSerializedObjectTest\$deserializeTest\$D"
}
val sf2 = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
val bytes = f.readBytes()
Assertions.assertThatThrownBy {
DeserializationInput(sf2).deserialize(SerializedBytes<D>(bytes))
}.isInstanceOf(NotSerializableException::class.java)
}
// Version of a serializer factory that will allow the class carpenter living on the
// factory to have a different whitelist applied to it than the factory
class TestSerializerFactory(wl1: ClassWhitelist, wl2: ClassWhitelist) :
SerializerFactory(wl1, ClassLoader.getSystemClassLoader()) {
override val classCarpenter = ClassCarpenter(ClassLoader.getSystemClassLoader(), wl2)
}
// This time have the serilization factory and the carpenter use different whitelists
@Test
fun deserializeTest2() {
data class D(val c: C2)
val path = EvolvabilityTests::class.java.getResource("StaticInitialisationOfSerializedObjectTest.deserializeTest2")
val f = File(path.toURI())
// Original version of the class for the serialised version of this class
//
//val sf1 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
//val sc = SerializationOutput(sf1).serialize(D(C2(20)))
//f.writeBytes(sc.bytes)
//println (path)
// whitelist to be used by the serialisation factory
class WL1 : ClassWhitelist {
override fun hasListed(type: Class<*>) =
type.name == "net.corda.nodeapi.internal.serialization.amqp" +
".StaticInitialisationOfSerializedObjectTest\$deserializeTest\$D"
}
// whitelist to be used by the carpenter
class WL2 : ClassWhitelist {
override fun hasListed(type: Class<*>) = true
}
val sf2 = TestSerializerFactory(WL1(), WL2())
val bytes = f.readBytes()
// Deserializing should throw because C is not on the whitelist NOT because
// we ever went anywhere near statically constructing it prior to not actually
// creating an instance of it
Assertions.assertThatThrownBy {
DeserializationInput(sf2).deserialize(SerializedBytes<D>(bytes))
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.internal.uncheckedCast
import net.corda.nodeapi.internal.serialization.AllWhitelist
import org.junit.Test
import java.beans.Introspector
@ -323,7 +324,6 @@ class ClassCarpenterTest {
}
@Test
@Suppress("UNCHECKED_CAST")
fun `int array`() {
val className = "iEnjoyPotato"
val schema = ClassSchema(
@ -356,7 +356,6 @@ class ClassCarpenterTest {
}
@Test
@Suppress("UNCHECKED_CAST")
fun `integer array`() {
val className = "iEnjoyFlan"
val schema = ClassSchema(
@ -366,16 +365,15 @@ class ClassCarpenterTest {
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(arrayOf(1, 2, 3)) as SimpleFieldAccess
val arr = clazz.getMethod("getA").invoke(i)
val arr: Array<Int> = uncheckedCast(clazz.getMethod("getA").invoke(i))
assertEquals(1, (arr as Array<Int>)[0])
assertEquals(1, arr[0])
assertEquals(2, arr[1])
assertEquals(3, arr[2])
assertEquals("$className{a=[1, 2, 3]}", i.toString())
}
@Test
@Suppress("UNCHECKED_CAST")
fun `int array with ints`() {
val className = "iEnjoyCrumble"
val schema = ClassSchema(
@ -395,7 +393,6 @@ class ClassCarpenterTest {
}
@Test
@Suppress("UNCHECKED_CAST")
fun `multiple int arrays`() {
val className = "iEnjoyJam"
val schema = ClassSchema(
@ -417,7 +414,6 @@ class ClassCarpenterTest {
}
@Test
@Suppress("UNCHECKED_CAST")
fun `string array`() {
val className = "iEnjoyToast"
val schema = ClassSchema(
@ -427,7 +423,7 @@ class ClassCarpenterTest {
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(arrayOf("toast", "butter", "jam"))
val arr = clazz.getMethod("getA").invoke(i) as Array<String>
val arr: Array<String> = uncheckedCast(clazz.getMethod("getA").invoke(i))
assertEquals("toast", arr[0])
assertEquals("butter", arr[1])
@ -435,7 +431,6 @@ class ClassCarpenterTest {
}
@Test
@Suppress("UNCHECKED_CAST")
fun `string arrays`() {
val className = "iEnjoyToast"
val schema = ClassSchema(
@ -452,8 +447,8 @@ class ClassCarpenterTest {
"and on the side",
arrayOf("some pickles", "some fries"))
val arr1 = clazz.getMethod("getA").invoke(i) as Array<String>
val arr2 = clazz.getMethod("getC").invoke(i) as Array<String>
val arr1: Array<String> = uncheckedCast(clazz.getMethod("getA").invoke(i))
val arr2: Array<String> = uncheckedCast(clazz.getMethod("getC").invoke(i))
assertEquals("bread", arr1[0])
assertEquals("spread", arr1[1])

View File

@ -0,0 +1,103 @@
package net.corda.nodeapi.internal.serialization.carpenter
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import org.assertj.core.api.Assertions
import org.junit.Ignore
import org.junit.Test
import java.io.NotSerializableException
class ClassCarpenterWhitelistTest {
// whitelisting a class on the class path will mean we will carpente up a class that
// contains it as a member
@Test
fun whitelisted() {
data class A(val a: Int)
class WL : ClassWhitelist {
private val allowedClasses = hashSetOf<String>(
A::class.java.name
)
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val cc = ClassCarpenter(whitelist = WL())
// if this works, the test works, if it throws then we're in a world of pain, we could
// go further but there are a lot of other tests that test weather we can build
// carpented objects
cc.build(ClassSchema("thing", mapOf("a" to NonNullableField(A::class.java))))
}
@Test
@Ignore("Currently the carpenter doesn't inspect it's whitelist so will carpent anything" +
"it's asked relying on the serializer factory to not ask for anything")
fun notWhitelisted() {
data class A(val a: Int)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = false
}
val cc = ClassCarpenter(whitelist = WL())
// Class A isn't on the whitelist, so we should fail to carpent it
Assertions.assertThatThrownBy {
cc.build(ClassSchema("thing", mapOf("a" to NonNullableField(A::class.java))))
}.isInstanceOf(NotSerializableException::class.java)
}
// despite now being whitelisted and on the class path, we will carpent this because
// it's marked as CordaSerializable
@Test
fun notWhitelistedButAnnotated() {
@CordaSerializable data class A(val a: Int)
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = false
}
val cc = ClassCarpenter(whitelist = WL())
// again, simply not throwing here is enough to show the test worked and the carpenter
// didn't reject the type even though it wasn't on the whitelist because it was
// annotated properly
cc.build(ClassSchema("thing", mapOf("a" to NonNullableField(A::class.java))))
}
@Test
@Ignore("Currently the carpenter doesn't inspect it's whitelist so will carpent anything" +
"it's asked relying on the serializer factory to not ask for anything")
fun notWhitelistedButCarpented() {
// just have the white list reject *Everything* except ints
class WL : ClassWhitelist {
override fun hasListed(type: Class<*>) = type.name == "int"
}
val cc = ClassCarpenter(whitelist = WL())
val schema1a = ClassSchema("thing1a", mapOf("a" to NonNullableField(Int::class.java)))
// thing 1 won't be set as corda serializable, meaning we won't build schema 2
schema1a.unsetCordaSerializable()
val clazz1a = cc.build(schema1a)
val schema2 = ClassSchema("thing2", mapOf("a" to NonNullableField(clazz1a)))
// thing 2 references thing 1 which wasn't carpented as corda s erializable and thus
// this will fail
Assertions.assertThatThrownBy {
cc.build(schema2)
}.isInstanceOf(NotSerializableException::class.java)
// create a second type of schema1, this time leave it as corda serialzable
val schema1b = ClassSchema("thing1b", mapOf("a" to NonNullableField(Int::class.java)))
val clazz1b = cc.build(schema1b)
// since schema 1b was created as CordaSerializable this will work
ClassSchema("thing2", mapOf("a" to NonNullableField(clazz1b)))
}
}