mirror of
https://github.com/corda/corda.git
synced 2024-12-28 00:38:55 +00:00
Merge remote-tracking branch 'open/master' into shams-os-merge-120118
# Conflicts: # core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt # node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt # node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt # node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt # node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt # testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/DummyDealStateSchemaV1.kt
This commit is contained in:
commit
c71ef3139b
2
.gitignore
vendored
2
.gitignore
vendored
@ -37,6 +37,7 @@ lib/quasar.jar
|
|||||||
.idea/markdown-navigator
|
.idea/markdown-navigator
|
||||||
.idea/runConfigurations
|
.idea/runConfigurations
|
||||||
.idea/dictionaries
|
.idea/dictionaries
|
||||||
|
.idea/codeStyles/
|
||||||
/gradle-plugins/.idea/
|
/gradle-plugins/.idea/
|
||||||
|
|
||||||
# Include the -parameters compiler option by default in IntelliJ required for serialization.
|
# Include the -parameters compiler option by default in IntelliJ required for serialization.
|
||||||
@ -88,6 +89,7 @@ crashlytics-build.properties
|
|||||||
|
|
||||||
# docs related
|
# docs related
|
||||||
docs/virtualenv/
|
docs/virtualenv/
|
||||||
|
virtualenv/
|
||||||
|
|
||||||
# bft-smart
|
# bft-smart
|
||||||
**/config/currentView
|
**/config/currentView
|
||||||
|
@ -12,11 +12,20 @@ import java.util.jar.JarInputStream
|
|||||||
* An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
|
* An attachment is a ZIP (or an optionally signed JAR) that contains one or more files. Attachments are meant to
|
||||||
* contain public static data which can be referenced from transactions and utilised from contracts. Good examples
|
* contain public static data which can be referenced from transactions and utilised from contracts. Good examples
|
||||||
* of how attachments are meant to be used include:
|
* of how attachments are meant to be used include:
|
||||||
|
*
|
||||||
* - Calendar data
|
* - Calendar data
|
||||||
* - Fixes (e.g. LIBOR)
|
* - Fixes (e.g. LIBOR)
|
||||||
* - Smart contract code
|
* - Smart contract code
|
||||||
* - Legal documents
|
* - Legal documents
|
||||||
* - Facts generated by oracles which might be reused a lot
|
* - Facts generated by oracles which might be reused a lot.
|
||||||
|
*
|
||||||
|
* At the moment, non-ZIP attachments are not supported. Support may come in a future release. Using ZIP files for
|
||||||
|
* attachments makes it easy to ensure data on the ledger is compressed, which is useful considering that attachments
|
||||||
|
* may be widely replicated around the network. It also allows the jarsigner tool to be used to sign an attachment
|
||||||
|
* using ordinary certificates of the kind that many organisations already have, and improves the efficiency of
|
||||||
|
* attachment resolution in cases where the attachment is logically made up of many small files - e.g. is bytecode.
|
||||||
|
* Finally, using ZIPs ensures files have a timestamp associated with them, and enables informational attachments
|
||||||
|
* to be password protected (although in current releases password protected ZIPs are likely to fail to work).
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
interface Attachment : NamedByHash {
|
interface Attachment : NamedByHash {
|
||||||
|
@ -407,7 +407,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(InvalidKeyException::class, SignatureException::class)
|
@Throws(InvalidKeyException::class, SignatureException::class)
|
||||||
fun doSign(privateKey: PrivateKey, clearData: ByteArray) = doSign(findSignatureScheme(privateKey), privateKey, clearData)
|
fun doSign(privateKey: PrivateKey, clearData: ByteArray): ByteArray = doSign(findSignatureScheme(privateKey), privateKey, clearData)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known schemeCodeName [String].
|
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known schemeCodeName [String].
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.crypto.DigitalSignature
|
||||||
|
import net.corda.core.crypto.SignedData
|
||||||
|
import net.corda.core.crypto.verify
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
|
// TODO: Rename this to DigitalSignature.WithCert once we're happy for it to be public API. The methods will need documentation
|
||||||
|
// and the correct exceptions will be need to be annotated
|
||||||
|
/** A digital signature with attached certificate of the public key. */
|
||||||
|
class DigitalSignatureWithCert(val by: X509Certificate, bytes: ByteArray) : DigitalSignature(bytes) {
|
||||||
|
fun verify(content: ByteArray): Boolean = by.publicKey.verify(content, this)
|
||||||
|
fun verify(content: OpaqueBytes): Boolean = verify(content.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Similar to [SignedData] but instead of just attaching the public key, the certificate for the key is attached instead. */
|
||||||
|
@CordaSerializable
|
||||||
|
class SignedDataWithCert<T : Any>(val raw: SerializedBytes<T>, val sig: DigitalSignatureWithCert) {
|
||||||
|
fun verified(): T {
|
||||||
|
sig.verify(raw)
|
||||||
|
return uncheckedCast(raw.deserialize<Any>())
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,14 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -27,6 +30,8 @@ import java.nio.charset.Charset
|
|||||||
import java.nio.charset.StandardCharsets.UTF_8
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
import java.nio.file.*
|
import java.nio.file.*
|
||||||
import java.nio.file.attribute.FileAttribute
|
import java.nio.file.attribute.FileAttribute
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.temporal.Temporal
|
import java.time.temporal.Temporal
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -308,6 +313,19 @@ fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, seri
|
|||||||
val KClass<*>.packageName: String get() = java.`package`.name
|
val KClass<*>.packageName: String get() = java.`package`.name
|
||||||
|
|
||||||
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
|
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
|
||||||
|
|
||||||
|
fun HttpURLConnection.checkOkResponse() {
|
||||||
|
if (responseCode != 200) {
|
||||||
|
val message = errorStream.use { it.reader().readText() }
|
||||||
|
throw IOException("Response Code $responseCode: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> HttpURLConnection.responseAs(): T {
|
||||||
|
checkOkResponse()
|
||||||
|
return inputStream.use { it.readBytes() }.deserialize()
|
||||||
|
}
|
||||||
|
|
||||||
/** Analogous to [Thread.join]. */
|
/** Analogous to [Thread.join]. */
|
||||||
fun ExecutorService.join() {
|
fun ExecutorService.join() {
|
||||||
shutdown() // Do not change to shutdownNow, tests use this method to assert the executor has no more tasks.
|
shutdown() // Do not change to shutdownNow, tests use this method to assert the executor has no more tasks.
|
||||||
@ -336,3 +354,9 @@ val CordaX500Name.x500Name: X500Name
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val CordaX500Name.Companion.unspecifiedCountry
|
val CordaX500Name.Companion.unspecifiedCountry
|
||||||
get() = "ZZ"
|
get() = "ZZ"
|
||||||
|
|
||||||
|
fun <T : Any> T.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert<T> {
|
||||||
|
val serialised = serialize()
|
||||||
|
val signature = Crypto.doSign(privateKey, serialised.bytes)
|
||||||
|
return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature))
|
||||||
|
}
|
||||||
|
@ -4,7 +4,9 @@ import net.corda.core.contracts.*
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import org.hibernate.annotations.Type
|
import org.hibernate.annotations.Type
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.persistence.*
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.MappedSuperclass
|
||||||
|
import javax.persistence.Transient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JPA representation of the common schema entities
|
* JPA representation of the common schema entities
|
||||||
|
@ -6,6 +6,9 @@ from the previous milestone release.
|
|||||||
|
|
||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
* JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the
|
||||||
|
`participants` collection need to be moved to the actual class. This allows to properly specify the unique table name per a collection.
|
||||||
|
See: DummyDealStateSchemaV1.PersistentDummyDealState
|
||||||
|
|
||||||
* X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role
|
* X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role
|
||||||
hierarchy is now enforced in the validation code. See ``net.corda.core.internal.CertRole`` for the current implementation
|
hierarchy is now enforced in the validation code. See ``net.corda.core.internal.CertRole`` for the current implementation
|
||||||
|
@ -12,7 +12,7 @@ rpcAddress : "my-corda-node:10003"
|
|||||||
webAddress : "localhost:10004"
|
webAddress : "localhost:10004"
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
rpcUsers : [
|
rpcUsers : [
|
||||||
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }
|
||||||
]
|
]
|
||||||
devMode : true
|
devMode : true
|
||||||
// certificateSigningService : "https://testnet.certificate.corda.net"
|
// certificateSigningService : "https://testnet.certificate.corda.net"
|
||||||
|
@ -1,84 +1,78 @@
|
|||||||
Network Map
|
Network Map
|
||||||
===========
|
===========
|
||||||
|
|
||||||
The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
|
The network map is a collection of signed ``NodeInfo`` objects (signed by the node it represents and thus tamper-proof)
|
||||||
There are two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
forming the set of reachable nodes in a compatbility zone. A node can receive these objects from two sources:
|
||||||
|
|
||||||
1. the REST protocol with the network map service, which also provides a publishing API,
|
1. The HTTP network map service if the ``compatibilityZoneURL`` config key is specified.
|
||||||
|
2. The ``additional-node-infos`` directory within the node's directory.
|
||||||
|
|
||||||
2. the ``additional-node-infos`` directory.
|
HTTP network map service
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
If the node is configured with the ``compatibilityZoneURL`` config then it first uploads its own signed ``NodeInfo``
|
||||||
|
to the server (and each time it changes on startup) and then proceeds to download the entire network map. The network map
|
||||||
|
consists of a list of ``NodeInfo`` hashes. The node periodically polls for the network map (based on the HTTP cache expiry
|
||||||
|
header) and any new hash entries are downloaded and cached. Entries which no longer exist are deleted from the node's cache.
|
||||||
|
|
||||||
Protocol Design
|
The set of REST end-points for the network map service are as follows.
|
||||||
---------------
|
|
||||||
The node info publishing protocol:
|
|
||||||
|
|
||||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| Request method | Path | Description |
|
||||||
* Serialise the signed data and POST the data to the network map server.
|
+================+=========================================+==============================================================================================================================================+
|
||||||
|
| POST | /network-map/publish | For the node to upload its signed ``NodeInfo`` object to the network map. |
|
||||||
* The network map server validates the signature and acknowledges the registration with a HTTP 200 response, it will return HTTP 400 "Bad Request" if the data failed validation or if the public key wasn't registered with the network.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| GET | /network-map | Retrieve the current signed network map object. The entire object is signed with the network map certificate which is also attached. |
|
||||||
* The network map server will sign and distribute the new network map periodically.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| GET | /network-map/node-info/{hash} | Retrieve a signed ``NodeInfo`` as specified in the network map object. |
|
||||||
Node side network map update protocol:
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| GET | /network-map/network-parameters/{hash} | Retrieve the signed network parameters (see below). The entire object is signed with the network map certificate which is also attached. |
|
||||||
* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
|
||||||
* The network map service returns a signed ``NetworkMap`` object which looks as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
data class NetworkMap {
|
|
||||||
val nodeInfoHashes: List<SecureHash>,
|
|
||||||
val networkParametersHash: SecureHash
|
|
||||||
}
|
|
||||||
|
|
||||||
The object contains list of node info hashes and hash of the network parameters data structure (without the signatures).
|
|
||||||
|
|
||||||
* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``.
|
|
||||||
|
|
||||||
Network Map service REST API:
|
|
||||||
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| Request method | Path | Description |
|
|
||||||
+================+===================================+========================================================================================================================================================+
|
|
||||||
| POST | /network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| GET | /network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and ``NetworkParameters`` hash. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| GET | /network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| GET | /network-map/parameters/{hash} | Retrieve ``NetworkParameters`` object with the same hash. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
|
|
||||||
TODO: Access control of the network map will be added in the future.
|
|
||||||
|
|
||||||
|
|
||||||
The ``additional-node-infos`` directory
|
The ``additional-node-infos`` directory
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
|
|
||||||
|
|
||||||
Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
|
Alongside the HTTP network map service, or as a replacement if the node isn't connected to one, the node polls the
|
||||||
|
contents of the ``additional-node-infos`` directory located in its base directory. Each file is expected to be the same
|
||||||
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
|
signed ``NodeInfo`` object that the network map service vends. These are automtically added to the node's cache and can
|
||||||
|
be used to supplement or replace the HTTP network map. If the same node is advertised through both mechanisms then the
|
||||||
Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
|
latest one is taken.
|
||||||
|
|
||||||
|
On startup the node generates its own signed node info file, filename of the format ``nodeInfo-${hash}``. To create a simple
|
||||||
|
network without the HTTP network map service then simply place this file in the ``additional-node-infos`` directory
|
||||||
|
of every node that's part of this network.
|
||||||
|
|
||||||
Network parameters
|
Network parameters
|
||||||
------------------
|
------------------
|
||||||
Network parameters are constants that every node participating in the network needs to agree on and use for interop purposes.
|
|
||||||
The structure is distributed as a file containing serialized ``SignedData<NetworkParameters>`` with a signature from
|
|
||||||
a sub-key of the compatibility zone root cert. Network map advertises the hash of currently used network parameters.
|
|
||||||
The ``NetworkParameters`` structure contains:
|
|
||||||
* ``minimumPlatformVersion`` - minimum version of Corda platform that is required for nodes in the network.
|
|
||||||
* ``notaries`` - list of well known and trusted notary identities with information on validation type.
|
|
||||||
* ``maxMessageSize`` - maximum P2P message size sent over the wire in bytes.
|
|
||||||
* ``maxTransactionSize`` - maximum permitted transaction size in bytes.
|
|
||||||
* ``modifiedTime`` - the time the network parameters were created by the CZ operator.
|
|
||||||
* ``epoch`` - version number of the network parameters. Starting from 1, this will always increment on each new set of parameters.
|
|
||||||
|
|
||||||
The set of parameters is still under development and we may find the need to add additional fields.
|
Network parameters are a set of values that every node participating in the network needs to agree on and use to
|
||||||
|
correctly interoperate with each other. If the node is using the HTTP network map service then on first startup it will
|
||||||
|
download the signed network parameters, cache it in a ``network-parameters`` file and apply them on the node.
|
||||||
|
|
||||||
|
.. warning:: If the ``network-parameters`` file is changed and no longer matches what the network map service is advertising
|
||||||
|
then the node will automatically shutdown. Resolution to this is to delete the incorrect file and restart the node so
|
||||||
|
that the parameters can be downloaded again.
|
||||||
|
|
||||||
|
.. note:: A future release will support the notion of network parameters changes.
|
||||||
|
|
||||||
|
If the node isn't using a HTTP network map service then it's expected the signed file is provided by some other means.
|
||||||
|
For such a scenario there is the network bootstrapper tool which in addition to generating the network parameters file
|
||||||
|
also distributes the node info files to the node directories. More information can be found in :doc:`setting-up-a-corda-network`.
|
||||||
|
|
||||||
|
The current set of network parameters:
|
||||||
|
|
||||||
|
:minimumPlatformVersion: The minimum platform version that the nodes must be running. Any node which is below this will
|
||||||
|
not start.
|
||||||
|
:notaries: List of identity and validation type (either validating or non-validating) of the notaries which are permitted
|
||||||
|
in the compatibility zone.
|
||||||
|
:maxMessageSize: Maximum allowed P2P message size sent over the wire in bytes. Any message larger than this will be
|
||||||
|
split up.
|
||||||
|
:maxTransactionSize: Maximum permitted transaction size in bytes.
|
||||||
|
:modifiedTime: The time when the network parameters were last modified by the compatibility zone operator.
|
||||||
|
:epoch: Version number of the network parameters. Starting from 1, this will always increment whenever any of the
|
||||||
|
parameters change.
|
||||||
|
|
||||||
|
.. note:: ``maxTransactionSize`` is currently not enforced in the node, but will be in a later release.
|
||||||
|
|
||||||
|
More parameters may be added in future releases.
|
||||||
|
@ -16,19 +16,31 @@ Unreleased
|
|||||||
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
|
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
|
||||||
annotations applied, have older and newer instances of that enumeration be understood.
|
annotations applied, have older and newer instances of that enumeration be understood.
|
||||||
|
|
||||||
* **AMQP Enabled**
|
* **AMQP Enabled**:
|
||||||
|
AMQP Serialization is now enabled for both peer to peer communication and the writing of states to the vault. This change
|
||||||
|
brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our
|
||||||
|
users.
|
||||||
|
|
||||||
AMQP Serialization is now enabled for both peer to peer communication and writing states to the vault. This change
|
Details on the AMQP serialization framework can be found in the :doc:`serialization` document :ref:`here <amqp_ref>`.
|
||||||
brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our
|
This provides an introduction and overview of the framework whilst more specific details on object evolution as it relates to
|
||||||
users.
|
serialization is similarly found in pages :doc:`serialization-default-evolution` and :doc:`serialization-enum-evolution`
|
||||||
|
respectively. Recommendations on how best to code CorDapps using your own :ref:`custom types <amqp_custom_types_ref>`.
|
||||||
|
|
||||||
|
.. note:: This release delivers the bulk of our transition from Kryo serialisation to AMQP serialisation. This means that many of the restrictions
|
||||||
|
that were documented in previous versions of Corda are now enforced. (https://docs.corda.net/releases/release-V1.0/serialization.html).
|
||||||
|
|
||||||
|
In particular, you are advised to review the section titled "Custom Types". To aid with the transition, we have included support
|
||||||
|
in this release for default construction of objects and their instantiation through getters as well as objects with inaccessible
|
||||||
|
private fields but it is not guaranteed that this support will continue into future versions; the restrictions documented at the
|
||||||
|
link above are the canonical source.
|
||||||
|
|
||||||
* **Custom Serializers**
|
* **Custom Serializers**
|
||||||
|
|
||||||
To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serializers
|
To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serializers
|
||||||
to be written for those classes. If needed, a proxy object can be created as an interim step that allows Corda's internal
|
to be written for those classes. If needed, a proxy object can be created as an interim step that allows Corda's internal
|
||||||
serializers to operate on those types.
|
serializers to operate on those types.
|
||||||
|
|
||||||
A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package
|
A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package
|
||||||
|
|
||||||
Release 2.0
|
Release 2.0
|
||||||
----------
|
----------
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
Object serialization
|
Object serialization
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
|
||||||
What is serialization (and deserialization)?
|
What is serialization (and deserialization)?
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ was a compelling use case for the definition and development of a custom format
|
|||||||
|
|
||||||
#. A desire to have a schema describing what has been serialized along-side the actual data:
|
#. A desire to have a schema describing what has been serialized along-side the actual data:
|
||||||
|
|
||||||
#. To assist with versioning, both in terms of being able to interpret long ago archivEd data (e.g. trades from
|
#. To assist with versioning, both in terms of being able to interpret long ago archived data (e.g. trades from
|
||||||
a decade ago, long after the code has changed) and between differing code versions.
|
a decade ago, long after the code has changed) and between differing code versions.
|
||||||
#. To make it easier to write user interfaces that can navigate the serialized form of data.
|
#. To make it easier to write user interfaces that can navigate the serialized form of data.
|
||||||
#. To support cross platform (non-JVM) interaction, where the format of a class file is not so easily interpreted.
|
#. To support cross platform (non-JVM) interaction, where the format of a class file is not so easily interpreted.
|
||||||
@ -76,7 +78,7 @@ Finally, for the checkpointing of flows Corda will continue to use the existing
|
|||||||
|
|
||||||
This separation of serialization schemes into different contexts allows us to use the most suitable framework for that context rather than
|
This separation of serialization schemes into different contexts allows us to use the most suitable framework for that context rather than
|
||||||
attempting to force a one size fits all approach. Where ``Kryo`` is more suited to the serialization of a programs stack frames, being more flexible
|
attempting to force a one size fits all approach. Where ``Kryo`` is more suited to the serialization of a programs stack frames, being more flexible
|
||||||
than our AMQP framework in what it can construct and serialize, that flexibility makes it exceptionally difficult to make secure. Conversly
|
than our AMQP framework in what it can construct and serialize, that flexibility makes it exceptionally difficult to make secure. Conversely
|
||||||
our AMQP framework allows us to concentrate on a robust a secure framework that can be reasoned about thus made safer with far fewer unforeseen
|
our AMQP framework allows us to concentrate on a robust a secure framework that can be reasoned about thus made safer with far fewer unforeseen
|
||||||
security holes.
|
security holes.
|
||||||
|
|
||||||
@ -282,22 +284,6 @@ serialised form
|
|||||||
|
|
||||||
val e2 = e.serialize().deserialize() // e2.c will be 20, not 100!!!
|
val e2 = e.serialize().deserialize() // e2.c will be 20, not 100!!!
|
||||||
|
|
||||||
.. warning:: Private properties in Kotlin classes render the class unserializable *unless* a public
|
|
||||||
getter is manually defined. For example:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
data class C(val a: Int, private val b: Int) {
|
|
||||||
// Without this C cannot be serialized
|
|
||||||
public fun getB() = b
|
|
||||||
}
|
|
||||||
|
|
||||||
.. note:: This is particularly relevant as IDE's can often point out where they believe a
|
|
||||||
property can be made private without knowing this can break Corda serialization. Should
|
|
||||||
this happen then a run time warning will be generated when the class fails to serialize
|
|
||||||
|
|
||||||
Setter Instantiation
|
Setter Instantiation
|
||||||
''''''''''''''''''''
|
''''''''''''''''''''
|
||||||
|
|
||||||
@ -330,6 +316,75 @@ For example:
|
|||||||
public void setC(int c) { this.c = c; }
|
public void setC(int c) { this.c = c; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Inaccessible Private Properties
|
||||||
|
```````````````````````````````
|
||||||
|
|
||||||
|
Whilst the Corda AMQP serialization framework supports private object properties without publicly
|
||||||
|
accessible getter methods this development idiom is strongly discouraged.
|
||||||
|
|
||||||
|
For example.
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
data class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
|
Java:
|
||||||
|
|
||||||
|
.. sourcecode:: Java
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public Integer a;
|
||||||
|
private Integer b;
|
||||||
|
|
||||||
|
C(Integer a, Integer b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
When designing stateful objects is should be remembered that they are not, despite appearances, traditional
|
||||||
|
programmatic constructs. They are signed over, transformed, serialised, and relationally mapped. As such,
|
||||||
|
all elements should be publicly accessible by design
|
||||||
|
|
||||||
|
.. warning:: IDEs will indiciate erroneously that properties can be given something other than public
|
||||||
|
visibility. Ignore this as whilst it will work, as discussed above there are many reasons why this isn't
|
||||||
|
a good idea and those are beyond the scope of the IDEs inference rules
|
||||||
|
|
||||||
|
Providing a public getter, as per the following example, is acceptable
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
data class C(val a: Int, private val b: Int) {
|
||||||
|
public fun getB() = b
|
||||||
|
}
|
||||||
|
|
||||||
|
Java:
|
||||||
|
|
||||||
|
.. sourcecode:: Java
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public Integer a;
|
||||||
|
private Integer b;
|
||||||
|
|
||||||
|
C(Integer a, Integer b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getB() {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Enums
|
Enums
|
||||||
`````
|
`````
|
||||||
|
|
||||||
|
@ -31,6 +31,26 @@ We also strongly recommend cross referencing with the :doc:`changelog` to confir
|
|||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* For existing contract ORM schemas that extend from `CommonSchemaV1.LinearState` or `CommonSchemaV1.FungibleState`,
|
||||||
|
you will need to explicitly map the `participants` collection to a database table. Previously this mapping was done in the
|
||||||
|
superclass, but that makes it impossible to properly configure the table name.
|
||||||
|
The required change is to add the ``override var participants: MutableSet<AbstractParty>? = null`` field to your class, and
|
||||||
|
add JPA mappings. For ex., see this example:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "cash_states_v2",
|
||||||
|
indexes = arrayOf(Index(name = "ccy_code_idx2", columnList = "ccy_code")))
|
||||||
|
class PersistentCashState(
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@Column(name = "participants")
|
||||||
|
@CollectionTable(name="cash_states_v2_participants", joinColumns = arrayOf(
|
||||||
|
JoinColumn(name = "output_index", referencedColumnName = "output_index"),
|
||||||
|
JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id")))
|
||||||
|
override var participants: MutableSet<AbstractParty>? = null,
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
@ -34,13 +34,8 @@ object DevIdentityGenerator {
|
|||||||
override val trustStorePassword get() = throw NotImplementedError("Not expected to be called")
|
override val trustStorePassword get() = throw NotImplementedError("Not expected to be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO The passwords for the dev key stores are spread everywhere and should be constants in a single location
|
|
||||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
|
||||||
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
|
||||||
val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
|
||||||
|
|
||||||
nodeSslConfig.certificatesDirectory.createDirectories()
|
nodeSslConfig.certificatesDirectory.createDirectories()
|
||||||
nodeSslConfig.createDevKeyStores(rootCert, intermediateCa, legalName)
|
nodeSslConfig.createDevKeyStores(legalName)
|
||||||
|
|
||||||
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
|
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
|
||||||
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
|
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
|
||||||
@ -54,16 +49,12 @@ object DevIdentityGenerator {
|
|||||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||||
val compositeKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
val compositeKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
||||||
|
|
||||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
|
||||||
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
|
||||||
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
|
||||||
|
|
||||||
keyPairs.zip(dirs) { keyPair, nodeDir ->
|
keyPairs.zip(dirs) { keyPair, nodeDir ->
|
||||||
val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey ->
|
val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey ->
|
||||||
X509Utilities.createCertificate(
|
X509Utilities.createCertificate(
|
||||||
CertificateType.SERVICE_IDENTITY,
|
CertificateType.SERVICE_IDENTITY,
|
||||||
intermediateCa.certificate,
|
DEV_INTERMEDIATE_CA.certificate,
|
||||||
intermediateCa.keyPair,
|
DEV_INTERMEDIATE_CA.keyPair,
|
||||||
notaryName.x500Principal,
|
notaryName.x500Principal,
|
||||||
publicKey)
|
publicKey)
|
||||||
}
|
}
|
||||||
@ -74,7 +65,7 @@ object DevIdentityGenerator {
|
|||||||
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
|
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
|
||||||
keyPair.private,
|
keyPair.private,
|
||||||
"cordacadevkeypass".toCharArray(),
|
"cordacadevkeypass".toCharArray(),
|
||||||
arrayOf(serviceKeyCert, intermediateCa.certificate, rootCert))
|
arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||||
keystore.save(distServKeyStoreFile, "cordacadevpass")
|
keystore.save(distServKeyStoreFile, "cordacadevpass")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,12 +9,17 @@ import org.bouncycastle.asn1.x509.GeneralName
|
|||||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||||
import org.bouncycastle.asn1.x509.NameConstraints
|
import org.bouncycastle.asn1.x509.NameConstraints
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
|
// TODO Merge this file and DevIdentityGenerator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
|
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
|
||||||
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
|
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
|
||||||
*/
|
*/
|
||||||
fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
|
fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name,
|
||||||
|
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
|
||||||
|
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) {
|
||||||
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
|
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
|
||||||
|
|
||||||
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
|
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
|
||||||
@ -39,6 +44,17 @@ fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair {
|
||||||
|
val keyPair = Crypto.generateKeyPair()
|
||||||
|
val cert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.NETWORK_MAP,
|
||||||
|
rootCa.certificate,
|
||||||
|
rootCa.keyPair,
|
||||||
|
X500Principal("CN=Network Map,O=R3 Ltd,L=London,C=GB"),
|
||||||
|
keyPair.public)
|
||||||
|
return CertificateAndKeyPair(cert, keyPair)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a dev node CA cert, as a sub-cert of the given [intermediateCa], and matching key pair using the given
|
* Create a dev node CA cert, as a sub-cert of the given [intermediateCa], and matching key pair using the given
|
||||||
* [CordaX500Name] as the cert subject.
|
* [CordaX500Name] as the cert subject.
|
||||||
@ -55,3 +71,16 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500N
|
|||||||
nameConstraints = nameConstraints)
|
nameConstraints = nameConstraints)
|
||||||
return CertificateAndKeyPair(cert, keyPair)
|
return CertificateAndKeyPair(cert, keyPair)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_INTERMEDIATE_CA)
|
||||||
|
|
||||||
|
val DEV_ROOT_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_ROOT_CA)
|
||||||
|
|
||||||
|
// We need a class so that we can get hold of the class loader
|
||||||
|
internal object DevCaHelper {
|
||||||
|
fun loadDevCa(alias: String): CertificateAndKeyPair {
|
||||||
|
// TODO: Should be identity scheme
|
||||||
|
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||||
|
return caKeyStore.getCertificateAndKeyPair(alias, "cordacadevkeypass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,12 +13,14 @@ import java.security.SignatureException
|
|||||||
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
|
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
|
||||||
* to be in the same order as the identities.
|
* to be in the same order as the identities.
|
||||||
*/
|
*/
|
||||||
|
// TODO Move this to net.corda.nodeapi.internal.network
|
||||||
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
|
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
|
||||||
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
|
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
|
||||||
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
|
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
|
||||||
// public keys.
|
// public keys.
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<DigitalSignature>) {
|
class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<DigitalSignature>) {
|
||||||
|
// TODO Add root cert param (or TrustAnchor) to make sure all the identities belong to the same root
|
||||||
fun verified(): NodeInfo {
|
fun verified(): NodeInfo {
|
||||||
val nodeInfo = raw.deserialize()
|
val nodeInfo = raw.deserialize()
|
||||||
val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }
|
val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
package net.corda.nodeapi.internal.network
|
package net.corda.nodeapi.internal.network
|
||||||
|
|
||||||
import net.corda.core.crypto.DigitalSignature
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.verify
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
|
import net.corda.core.internal.SignedDataWithCert
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SerializedBytes
|
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import java.security.SignatureException
|
|
||||||
import java.security.cert.CertPathValidatorException
|
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@ -55,30 +51,8 @@ data class NetworkParameters(
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||||
|
|
||||||
/**
|
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
|
||||||
* A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data
|
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
|
||||||
* contained within.
|
X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)
|
||||||
*/
|
return verified()
|
||||||
@CordaSerializable
|
}
|
||||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val signature: DigitalSignatureWithCert) {
|
|
||||||
/**
|
|
||||||
* Return the deserialized NetworkMap if the signature and certificate can be verified.
|
|
||||||
*
|
|
||||||
* @throws CertPathValidatorException if the certificate path is invalid.
|
|
||||||
* @throws SignatureException if the signature is invalid.
|
|
||||||
*/
|
|
||||||
@Throws(SignatureException::class, CertPathValidatorException::class)
|
|
||||||
fun verified(trustedRoot: X509Certificate?): NetworkMap {
|
|
||||||
signature.by.publicKey.verify(raw.bytes, signature)
|
|
||||||
if (trustedRoot != null) {
|
|
||||||
// Assume network map cert is under the default trust root.
|
|
||||||
X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot)
|
|
||||||
}
|
|
||||||
return raw.deserialize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This class should reside in the [DigitalSignature] class.
|
|
||||||
// TODO: Removing the val from signatureBytes causes serialisation issues
|
|
||||||
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
|
|
||||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
|
@ -1,35 +1,32 @@
|
|||||||
package net.corda.nodeapi.internal.network
|
package net.corda.nodeapi.internal.network
|
||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
|
||||||
import net.corda.core.crypto.SignedData
|
|
||||||
import net.corda.core.crypto.sign
|
|
||||||
import net.corda.core.internal.copyTo
|
import net.corda.core.internal.copyTo
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.internal.signWithCert
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.FileAlreadyExistsException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.security.KeyPair
|
|
||||||
|
|
||||||
class NetworkParametersCopier(
|
class NetworkParametersCopier(
|
||||||
networkParameters: NetworkParameters,
|
networkParameters: NetworkParameters,
|
||||||
signingKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
|
networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
|
||||||
overwriteFile: Boolean = false
|
overwriteFile: Boolean = false
|
||||||
) {
|
) {
|
||||||
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
|
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
|
||||||
private val serializedNetworkParameters = networkParameters.let {
|
private val serialisedSignedNetParams = networkParameters.signWithCert(
|
||||||
val serialize = it.serialize()
|
networkMapCa.keyPair.private,
|
||||||
val signature = signingKeyPair.sign(serialize)
|
networkMapCa.certificate
|
||||||
SignedData(serialize, signature).serialize()
|
).serialize()
|
||||||
}
|
|
||||||
|
|
||||||
fun install(nodeDir: Path) {
|
fun install(nodeDir: Path) {
|
||||||
try {
|
try {
|
||||||
serializedNetworkParameters.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
|
serialisedSignedNetParams.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
|
||||||
} catch (e: FileAlreadyExistsException) {
|
} catch (e: FileAlreadyExistsException) {
|
||||||
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
|
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
|
||||||
// ignore this exception as we're happy with the existing file.
|
// ignore this exception as we're happy with the existing file.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,6 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>): T =
|
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>): T =
|
||||||
deserialize(bytes, T::class.java)
|
deserialize(bytes, T::class.java)
|
||||||
|
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
|
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
|
||||||
deserializeAndReturnEnvelope(bytes, T::class.java)
|
deserializeAndReturnEnvelope(bytes, T::class.java)
|
||||||
|
@ -106,7 +106,7 @@ class EnumEvolutionSerializer(
|
|||||||
// to the name as it exists. We want to test any new constants have been added to the end
|
// to the name as it exists. We want to test any new constants have been added to the end
|
||||||
// of the enum class
|
// of the enum class
|
||||||
val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices
|
val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices
|
||||||
.associateBy ({ it.value.toInt() }, { conversions[it.name] }))
|
.associateBy({ it.value.toInt() }, { conversions[it.name] }))
|
||||||
|
|
||||||
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
|
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
|
||||||
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")
|
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")
|
||||||
@ -133,4 +133,4 @@ class EnumEvolutionSerializer(
|
|||||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||||
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ class EvolutionSerializer(
|
|||||||
old.fields.forEach {
|
old.fields.forEach {
|
||||||
val returnType = it.getTypeAsClass(factory.classloader)
|
val returnType = it.getTypeAsClass(factory.classloader)
|
||||||
oldArgs[it.name] = OldParam(
|
oldArgs[it.name] = OldParam(
|
||||||
returnType, idx++, PropertySerializer.make(it.name, null, returnType, factory))
|
returnType, idx++, PropertySerializer.make(it.name, PublicPropertyReader(null), returnType, factory))
|
||||||
}
|
}
|
||||||
|
|
||||||
val readers = constructor.parameters.map {
|
val readers = constructor.parameters.map {
|
||||||
@ -130,3 +130,34 @@ class EvolutionSerializer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of this type are injected into a [SerializerFactory] at creation time to dictate the
|
||||||
|
* behaviour of evolution within that factory. Under normal circumstances this will simply
|
||||||
|
* be an object that returns an [EvolutionSerializer]. Of course, any implementation that
|
||||||
|
* extends this class can be written to invoke whatever behaviour is desired.
|
||||||
|
*/
|
||||||
|
abstract class EvolutionSerializerGetterBase {
|
||||||
|
abstract fun getEvolutionSerializer(
|
||||||
|
factory: SerializerFactory,
|
||||||
|
typeNotation: TypeNotation,
|
||||||
|
newSerializer: AMQPSerializer<Any>,
|
||||||
|
schemas: SerializationSchemas): AMQPSerializer<Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The normal use case for generating an [EvolutionSerializer]'s based on the differences
|
||||||
|
* between the received schema and the class as it exists now on the class path,
|
||||||
|
*/
|
||||||
|
class EvolutionSerializerGetter : EvolutionSerializerGetterBase() {
|
||||||
|
override fun getEvolutionSerializer(factory: SerializerFactory,
|
||||||
|
typeNotation: TypeNotation,
|
||||||
|
newSerializer: AMQPSerializer<Any>,
|
||||||
|
schemas: SerializationSchemas): AMQPSerializer<Any> =
|
||||||
|
factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) {
|
||||||
|
when (typeNotation) {
|
||||||
|
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory)
|
||||||
|
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
|||||||
propertiesForSerialization(kotlinConstructor, clazz, factory)
|
propertiesForSerialization(kotlinConstructor, clazz, factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPropertySerializers() = propertySerializers
|
||||||
|
|
||||||
private val typeName = nameForType(clazz)
|
private val typeName = nameForType(clazz)
|
||||||
|
|
||||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
||||||
|
@ -1,17 +1,74 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.loggerFor
|
||||||
import org.apache.qpid.proton.amqp.Binary
|
import org.apache.qpid.proton.amqp.Binary
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
import java.lang.reflect.Field
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.jvm.javaGetter
|
import kotlin.reflect.jvm.javaGetter
|
||||||
|
import kotlin.reflect.jvm.kotlinProperty
|
||||||
|
|
||||||
|
abstract class PropertyReader {
|
||||||
|
abstract fun read(obj: Any?): Any?
|
||||||
|
abstract fun isNullable(): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
|
||||||
|
init {
|
||||||
|
readMethod?.isAccessible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Method.returnsNullable(): Boolean {
|
||||||
|
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.
|
||||||
|
|
||||||
|
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(obj: Any?): Any? {
|
||||||
|
return readMethod!!.invoke(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isNullable(): Boolean = readMethod?.returnsNullable() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader() {
|
||||||
|
init {
|
||||||
|
loggerFor<PropertySerializer>().warn("Create property Serializer for private property '${field.name}' not "
|
||||||
|
+ "exposed by a getter on class '$parentType'\n"
|
||||||
|
+ "\tNOTE: This behaviour will be deprecated at some point in the future and a getter required")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(obj: Any?): Any? {
|
||||||
|
field.isAccessible = true
|
||||||
|
val rtn = field.get(obj)
|
||||||
|
field.isAccessible = false
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isNullable() = try {
|
||||||
|
field.kotlinProperty?.returnType?.isMarkedNullable ?: false
|
||||||
|
} 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.
|
||||||
|
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for serialization of a property of an object.
|
* Base class for serialization of a property of an object.
|
||||||
*/
|
*/
|
||||||
sealed class PropertySerializer(val name: String, val readMethod: Method?, val resolvedType: Type) {
|
sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) {
|
||||||
abstract fun writeClassInfo(output: SerializationOutput)
|
abstract fun writeClassInfo(output: SerializationOutput)
|
||||||
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
||||||
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
||||||
@ -44,25 +101,11 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateMandatory(): Boolean {
|
private fun generateMandatory(): Boolean {
|
||||||
return isJVMPrimitive || readMethod?.returnsNullable() == false
|
return isJVMPrimitive || !(propertyReader.isNullable())
|
||||||
}
|
|
||||||
|
|
||||||
private fun Method.returnsNullable(): Boolean {
|
|
||||||
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 {
|
companion object {
|
||||||
private val logger = contextLogger()
|
fun make(name: String, readMethod: PropertyReader, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
|
||||||
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
|
|
||||||
readMethod?.isAccessible = true
|
|
||||||
if (SerializerFactory.isPrimitive(resolvedType)) {
|
if (SerializerFactory.isPrimitive(resolvedType)) {
|
||||||
return when (resolvedType) {
|
return when (resolvedType) {
|
||||||
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
|
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
|
||||||
@ -78,7 +121,8 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
* A property serializer for a complex type (another object).
|
* A property serializer for a complex type (another object).
|
||||||
*/
|
*/
|
||||||
class DescribedTypePropertySerializer(
|
class DescribedTypePropertySerializer(
|
||||||
name: String, readMethod: Method?,
|
name: String,
|
||||||
|
readMethod: PropertyReader,
|
||||||
resolvedType: Type,
|
resolvedType: Type,
|
||||||
private val lazyTypeSerializer: () -> AMQPSerializer<*>) : PropertySerializer(name, readMethod, resolvedType) {
|
private val lazyTypeSerializer: () -> AMQPSerializer<*>) : PropertySerializer(name, readMethod, resolvedType) {
|
||||||
// This is lazy so we don't get an infinite loop when a method returns an instance of the class.
|
// This is lazy so we don't get an infinite loop when a method returns an instance of the class.
|
||||||
@ -90,12 +134,15 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
|
override fun readProperty(
|
||||||
|
obj: Any?,
|
||||||
|
schemas: SerializationSchemas,
|
||||||
|
input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
|
||||||
input.readObjectOrNull(obj, schemas, resolvedType)
|
input.readObjectOrNull(obj, schemas, 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)
|
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val nameForDebug = "$name(${resolvedType.typeName})"
|
private val nameForDebug = "$name(${resolvedType.typeName})"
|
||||||
@ -104,7 +151,10 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
/**
|
/**
|
||||||
* A property serializer for most AMQP primitive type (Int, String, etc).
|
* A property serializer for most AMQP primitive type (Int, String, etc).
|
||||||
*/
|
*/
|
||||||
class AMQPPrimitivePropertySerializer(name: String, readMethod: Method?, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
|
class AMQPPrimitivePropertySerializer(
|
||||||
|
name: String,
|
||||||
|
readMethod: PropertyReader,
|
||||||
|
resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
|
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
|
||||||
@ -112,7 +162,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
||||||
val value = readMethod!!.invoke(obj)
|
val value = propertyReader.read(obj)
|
||||||
if (value is ByteArray) {
|
if (value is ByteArray) {
|
||||||
data.putObject(Binary(value))
|
data.putObject(Binary(value))
|
||||||
} else {
|
} else {
|
||||||
@ -126,7 +176,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
* value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit
|
* value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit
|
||||||
* casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs
|
* casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs
|
||||||
*/
|
*/
|
||||||
class AMQPCharPropertySerializer(name: String, readMethod: Method?) :
|
class AMQPCharPropertySerializer(name: String, readMethod: PropertyReader) :
|
||||||
PropertySerializer(name, readMethod, Character::class.java) {
|
PropertySerializer(name, readMethod, Character::class.java) {
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
@ -135,7 +185,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
||||||
val input = readMethod!!.invoke(obj)
|
val input = propertyReader.read(obj)
|
||||||
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
|
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,19 +346,54 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method concatentates various elements of the types recursively as unencoded strings into the hasher, effectively
|
// This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively
|
||||||
// creating a unique string for a type which we then hash in the calling function above.
|
// creating a unique string for a type which we then hash in the calling function above.
|
||||||
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
|
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
|
||||||
return if (type in alreadySeen) {
|
hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher {
|
||||||
|
|
||||||
|
// We don't include Example<?> and Example<T> where type is ? or T in this otherwise we
|
||||||
|
// generate different fingerprints for class Outer<T>(val a: Inner<T>) when serialising
|
||||||
|
// and deserializing (assuming deserialization is occurring in a factory that didn't
|
||||||
|
// serialise the object in the first place (and thus the cache lookup fails). This is also
|
||||||
|
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
|
||||||
|
return if (type in alreadySeen && (type !is SerializerFactory.AnyType) && (type !is TypeVariable<*>)) {
|
||||||
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
||||||
} else {
|
} else {
|
||||||
alreadySeen += type
|
alreadySeen += type
|
||||||
try {
|
try {
|
||||||
when (type) {
|
when (type) {
|
||||||
is SerializerFactory.AnyType -> hasher.putUnencodedChars(ANY_TYPE_HASH)
|
is ParameterizedType -> {
|
||||||
|
// Hash the rawType + params
|
||||||
|
val clazz = type.rawType as Class<*>
|
||||||
|
|
||||||
|
val startingHash = if (isCollectionOrMap(clazz)) {
|
||||||
|
hasher.putUnencodedChars(clazz.name)
|
||||||
|
} else {
|
||||||
|
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
|
||||||
|
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and concatenate the type data for each parameter type.
|
||||||
|
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
||||||
|
fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when
|
||||||
|
// looking at A and B from Example<A, B> (remember we call this function recursively). When
|
||||||
|
// serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but
|
||||||
|
// when deserializing we only have the wildcard placeholder ?, or AnyType
|
||||||
|
//
|
||||||
|
// Note, TypeVariable<*> used to be encoded as TYPE_VARIABLE_HASH but that again produces a
|
||||||
|
// differing fingerprint on serialisation and deserialization
|
||||||
|
is SerializerFactory.AnyType,
|
||||||
|
is TypeVariable<*> -> {
|
||||||
|
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
|
||||||
|
}
|
||||||
is Class<*> -> {
|
is Class<*> -> {
|
||||||
if (type.isArray) {
|
if (type.isArray) {
|
||||||
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory).putUnencodedChars(ARRAY_HASH)
|
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4)
|
||||||
|
.putUnencodedChars(ARRAY_HASH)
|
||||||
} else if (SerializerFactory.isPrimitive(type)) {
|
} else if (SerializerFactory.isPrimitive(type)) {
|
||||||
hasher.putUnencodedChars(type.name)
|
hasher.putUnencodedChars(type.name)
|
||||||
} else if (isCollectionOrMap(type)) {
|
} else if (isCollectionOrMap(type)) {
|
||||||
@ -377,32 +412,18 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
|
|||||||
// to the CorDapp but maybe reference to the JAR in the short term.
|
// to the CorDapp but maybe reference to the JAR in the short term.
|
||||||
hasher.putUnencodedChars(type.name)
|
hasher.putUnencodedChars(type.name)
|
||||||
} else {
|
} else {
|
||||||
fingerprintForObject(type, type, alreadySeen, hasher, factory)
|
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ParameterizedType -> {
|
|
||||||
// Hash the rawType + params
|
|
||||||
val clazz = type.rawType as Class<*>
|
|
||||||
val startingHash = if (isCollectionOrMap(clazz)) {
|
|
||||||
hasher.putUnencodedChars(clazz.name)
|
|
||||||
} else {
|
|
||||||
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
|
|
||||||
fingerprintForObject(type, type, alreadySeen, hasher, factory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ... and concatentate the type data for each parameter type.
|
|
||||||
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
|
||||||
fingerprintForType(paramType, type, alreadySeen, orig, factory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Hash the element type + some array hash
|
// Hash the element type + some array hash
|
||||||
is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
||||||
hasher, factory).putUnencodedChars(ARRAY_HASH)
|
hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH)
|
||||||
// TODO: include bounds
|
// TODO: include bounds
|
||||||
is TypeVariable<*> -> hasher.putUnencodedChars(type.name).putUnencodedChars(TYPE_VARIABLE_HASH)
|
is WildcardType -> {
|
||||||
is WildcardType -> hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
|
hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
|
||||||
|
}
|
||||||
else -> throw NotSerializableException("Don't know how to hash")
|
else -> throw NotSerializableException("Don't know how to hash")
|
||||||
}
|
}
|
||||||
} catch (e: NotSerializableException) {
|
} catch (e: NotSerializableException) {
|
||||||
@ -416,15 +437,21 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
|
|||||||
private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) &&
|
private fun isCollectionOrMap(type: Class<*>) = (Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type)) &&
|
||||||
!EnumSet::class.java.isAssignableFrom(type)
|
!EnumSet::class.java.isAssignableFrom(type)
|
||||||
|
|
||||||
private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>, hasher: Hasher, factory: SerializerFactory): Hasher {
|
private fun fingerprintForObject(
|
||||||
|
type: Type,
|
||||||
|
contextType: Type?,
|
||||||
|
alreadySeen: MutableSet<Type>,
|
||||||
|
hasher: Hasher,
|
||||||
|
factory: SerializerFactory,
|
||||||
|
offset: Int = 0): Hasher {
|
||||||
// Hash the class + properties + interfaces
|
// Hash the class + properties + interfaces
|
||||||
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
|
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
|
||||||
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters
|
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory).getters
|
||||||
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
||||||
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory)
|
fingerprintForType(prop.resolvedType, type, alreadySeen, orig, factory, offset+4)
|
||||||
.putUnencodedChars(prop.name)
|
.putUnencodedChars(prop.name)
|
||||||
.putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
.putUnencodedChars(if (prop.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
||||||
}
|
}
|
||||||
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
|
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) }
|
||||||
return hasher
|
return hasher
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import kotlin.reflect.full.findAnnotation
|
|||||||
import kotlin.reflect.full.primaryConstructor
|
import kotlin.reflect.full.primaryConstructor
|
||||||
import kotlin.reflect.jvm.isAccessible
|
import kotlin.reflect.jvm.isAccessible
|
||||||
import kotlin.reflect.jvm.javaType
|
import kotlin.reflect.jvm.javaType
|
||||||
|
import kotlin.reflect.jvm.kotlinProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation indicating a constructor to be used to reconstruct instances of a class during deserialization.
|
* Annotation indicating a constructor to be used to reconstruct instances of a class during deserialization.
|
||||||
@ -29,8 +30,8 @@ import kotlin.reflect.jvm.javaType
|
|||||||
annotation class ConstructorForDeserialization
|
annotation class ConstructorForDeserialization
|
||||||
|
|
||||||
data class ConstructorDestructorMethods(
|
data class ConstructorDestructorMethods(
|
||||||
val getters : Collection<PropertySerializer>,
|
val getters: Collection<PropertySerializer>,
|
||||||
val setters : Collection<Method?>)
|
val setters: Collection<Method?>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code for finding the constructor we will use for deserialization.
|
* Code for finding the constructor we will use for deserialization.
|
||||||
@ -100,26 +101,31 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
|
|||||||
for (param in kotlinConstructor.parameters) {
|
for (param in kotlinConstructor.parameters) {
|
||||||
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
||||||
|
|
||||||
val matchingProperty = properties[name] ?:
|
if (name in properties) {
|
||||||
try {
|
val matchingProperty = properties[name]!!
|
||||||
clazz.getDeclaredField(param.name)
|
|
||||||
throw NotSerializableException("Property '$name' or its getter is non public, this renders class '$clazz' unserializable")
|
|
||||||
} catch (e: NoSuchFieldException) {
|
|
||||||
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. " +
|
|
||||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the method has a getter in java.
|
// Check that the method has a getter in java.
|
||||||
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " +
|
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." +
|
"If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." +
|
||||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||||
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
||||||
rc += PropertySerializer.make(name, getter, returnType, factory)
|
rc += PropertySerializer.make(name, PublicPropertyReader(getter), returnType, factory)
|
||||||
|
} else {
|
||||||
|
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
|
try {
|
||||||
|
val field = (clazz.getDeclaredField(param.name))
|
||||||
|
|
||||||
|
rc += PropertySerializer.make(name, PrivatePropertyReader(field, type), field.genericType, factory)
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
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. " +
|
||||||
|
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConstructorDestructorMethods(rc, emptyList())
|
return ConstructorDestructorMethods(rc, emptyList())
|
||||||
@ -130,11 +136,11 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
|
|||||||
* and use those
|
* and use those
|
||||||
*/
|
*/
|
||||||
private fun propertiesForSerializationFromSetters(
|
private fun propertiesForSerializationFromSetters(
|
||||||
properties : Map<String, PropertyDescriptor>,
|
properties: Map<String, PropertyDescriptor>,
|
||||||
type: Type,
|
type: Type,
|
||||||
factory: SerializerFactory): ConstructorDestructorMethods {
|
factory: SerializerFactory): ConstructorDestructorMethods {
|
||||||
val getters : MutableList<PropertySerializer> = ArrayList(properties.size)
|
val getters: MutableList<PropertySerializer> = ArrayList(properties.size)
|
||||||
val setters : MutableList<Method?> = ArrayList(properties.size)
|
val setters: MutableList<Method?> = ArrayList(properties.size)
|
||||||
|
|
||||||
properties.forEach { property ->
|
properties.forEach { property ->
|
||||||
val getter: Method? = property.value.readMethod
|
val getter: Method? = property.value.readMethod
|
||||||
@ -146,8 +152,8 @@ private fun propertiesForSerializationFromSetters(
|
|||||||
// the getter / setter vs property as if there is a difference then that property isn't reported
|
// the getter / setter vs property as if there is a difference then that property isn't reported
|
||||||
// by the BEAN inspector and thus we don't consider that case here
|
// by the BEAN inspector and thus we don't consider that case here
|
||||||
|
|
||||||
getters += PropertySerializer.make(property.key, getter, resolveTypeVariables(getter.genericReturnType, type),
|
getters += PropertySerializer.make(property.key, PublicPropertyReader(getter),
|
||||||
factory)
|
resolveTypeVariables(getter.genericReturnType, type), factory)
|
||||||
setters += setter
|
setters += setter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,15 +165,22 @@ private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawG
|
|||||||
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
|
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): ConstructorDestructorMethods {
|
private fun propertiesForSerializationFromAbstract(
|
||||||
|
clazz: Class<*>,
|
||||||
|
type: Type,
|
||||||
|
factory: SerializerFactory): ConstructorDestructorMethods {
|
||||||
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
|
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
|
||||||
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.sortedBy { it.name }.filterNot { it is IndexedPropertyDescriptor }
|
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors
|
||||||
|
.filter { it.name != "class" }
|
||||||
|
.sortedBy { it.name }
|
||||||
|
.filterNot { it is IndexedPropertyDescriptor }
|
||||||
val rc: MutableList<PropertySerializer> = ArrayList(properties.size)
|
val rc: MutableList<PropertySerializer> = ArrayList(properties.size)
|
||||||
for (property in properties) {
|
for (property in properties) {
|
||||||
// Check that the method has a getter in java.
|
// Check that the method has a getter in java.
|
||||||
val getter = property.readMethod ?: throw NotSerializableException("Property has no getter method for ${property.name} of $clazz.")
|
val getter = property.readMethod ?: throw NotSerializableException(
|
||||||
|
"Property has no getter method for ${property.name} of $clazz.")
|
||||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||||
rc += PropertySerializer.make(property.name, getter, returnType, factory)
|
rc += PropertySerializer.make(property.name, PublicPropertyReader(getter), returnType, factory)
|
||||||
}
|
}
|
||||||
return ConstructorDestructorMethods(rc, emptyList())
|
return ConstructorDestructorMethods(rc, emptyList())
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,10 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory of serializers designed to be shared across threads and invocations.
|
* Factory of serializers designed to be shared across threads and invocations.
|
||||||
|
*
|
||||||
|
* @property evolutionSerializerGetter controls how evolution serializers are generated by the factory. The normal
|
||||||
|
* use case is an [EvolutionSerializer] type is returned. However, in some scenarios, primarily testing, this
|
||||||
|
* can be altered to fit the requirements of the test.
|
||||||
*/
|
*/
|
||||||
// TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency
|
// TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency
|
||||||
// TODO: maybe support for caching of serialized form of some core types for performance
|
// TODO: maybe support for caching of serialized form of some core types for performance
|
||||||
@ -33,27 +37,27 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
|
|||||||
// TODO: need to rethink matching of constructor to properties in relation to implementing interfaces and needing those properties etc.
|
// 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?
|
// 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
|
@ThreadSafe
|
||||||
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
open class SerializerFactory(
|
||||||
|
val whitelist: ClassWhitelist,
|
||||||
|
cl: ClassLoader,
|
||||||
|
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter()) {
|
||||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||||
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
|
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
|
||||||
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
private val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||||
|
|
||||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
||||||
|
|
||||||
val classloader: ClassLoader
|
val classloader: ClassLoader
|
||||||
get() = classCarpenter.classloader
|
get() = classCarpenter.classloader
|
||||||
|
|
||||||
private fun getEvolutionSerializer(
|
private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer<Any>,
|
||||||
typeNotation: TypeNotation,
|
schemas: SerializationSchemas)
|
||||||
newSerializer: AMQPSerializer<Any>,
|
= evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
|
||||||
schemas: SerializationSchemas): AMQPSerializer<Any> {
|
|
||||||
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
fun getSerializersByDescriptor() = serializersByDescriptor
|
||||||
when (typeNotation) {
|
|
||||||
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
|
fun getTransformsCache() = transformsCache
|
||||||
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, this, schemas)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up, and manufacture if necessary, a serializer for the given type.
|
* Look up, and manufacture if necessary, a serializer for the given type.
|
||||||
@ -92,7 +96,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
whitelist.requireWhitelisted(actualType)
|
whitelist.requireWhitelisted(actualType)
|
||||||
EnumSerializer(actualType, actualClass ?: declaredClass, this)
|
EnumSerializer(actualType, actualClass ?: declaredClass, this)
|
||||||
}
|
}
|
||||||
else -> makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
|
else -> {
|
||||||
|
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
|
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
|
||||||
@ -101,23 +107,23 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
|
* Try and infer concrete types for any generics type variables for the actual class encountered,
|
||||||
* type.
|
* based on the declared type.
|
||||||
*/
|
*/
|
||||||
// TODO: test GenericArrayType
|
// TODO: test GenericArrayType
|
||||||
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, declaredType: Type): Type? =
|
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>,
|
||||||
when (declaredType) {
|
declaredType: Type) : Type? = when (declaredType) {
|
||||||
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
|
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
|
||||||
// Nothing to infer, otherwise we'd have ParameterizedType
|
// Nothing to infer, otherwise we'd have ParameterizedType
|
||||||
is Class<*> -> actualClass
|
is Class<*> -> actualClass
|
||||||
is GenericArrayType -> {
|
is GenericArrayType -> {
|
||||||
val declaredComponent = declaredType.genericComponentType
|
val declaredComponent = declaredType.genericComponentType
|
||||||
inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray()
|
inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray()
|
||||||
}
|
}
|
||||||
is TypeVariable<*> -> actualClass
|
is TypeVariable<*> -> actualClass
|
||||||
is WildcardType -> actualClass
|
is WildcardType -> actualClass
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
|
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
|
||||||
@ -213,9 +219,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
try {
|
try {
|
||||||
val serialiser = processSchemaEntry(typeNotation)
|
val serialiser = processSchemaEntry(typeNotation)
|
||||||
|
|
||||||
// if we just successfully built a serialiser for the type but the type fingerprint
|
// if we just successfully built a serializer for the type but the type fingerprint
|
||||||
// doesn't match that of the serialised object then we are dealing with different
|
// doesn't match that of the serialised object then we are dealing with different
|
||||||
// instance of the class, as such we need to build an EvolutionSerialiser
|
// instance of the class, as such we need to build an EvolutionSerializer
|
||||||
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
|
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
|
||||||
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
|
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
|
||||||
}
|
}
|
||||||
@ -336,7 +342,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
"${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}"
|
"${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}"
|
||||||
} else type.name
|
} else type.name
|
||||||
}
|
}
|
||||||
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
|
is ParameterizedType -> {
|
||||||
|
"${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
|
||||||
|
}
|
||||||
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
|
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
|
||||||
is WildcardType -> "?"
|
is WildcardType -> "?"
|
||||||
is TypeVariable<*> -> "?"
|
is TypeVariable<*> -> "?"
|
||||||
@ -381,3 +389,4 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
override fun toString(): String = "?"
|
override fun toString(): String = "?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
|
|||||||
* @param sf the [SerializerFactory] building this transform set. Needed as each can define it's own
|
* @param sf the [SerializerFactory] building this transform set. Needed as each can define it's own
|
||||||
* class loader and this dictates which classes we can and cannot see
|
* class loader and this dictates which classes we can and cannot see
|
||||||
*/
|
*/
|
||||||
fun get(name: String, sf: SerializerFactory) = sf.transformsCache.computeIfAbsent(name) {
|
fun get(name: String, sf: SerializerFactory) = sf.getTransformsCache().computeIfAbsent(name) {
|
||||||
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
|
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
|
||||||
try {
|
try {
|
||||||
val clazz = sf.classloader.loadClass(name)
|
val clazz = sf.classloader.loadClass(name)
|
||||||
|
@ -25,7 +25,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
|
|||||||
val constructor = constructorForDeserialization(obj.javaClass)
|
val constructor = constructorForDeserialization(obj.javaClass)
|
||||||
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
|
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
|
||||||
for (prop in props.getters) {
|
for (prop in props.getters) {
|
||||||
extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
|
extraProperties[prop.name] = prop.propertyReader.read(obj)
|
||||||
}
|
}
|
||||||
} catch (e: NotSerializableException) {
|
} catch (e: NotSerializableException) {
|
||||||
logger.warn("Unexpected exception", e)
|
logger.warn("Unexpected exception", e)
|
||||||
|
@ -2,10 +2,12 @@ package net.corda.nodeapi.internal.serialization.amqp;
|
|||||||
|
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist;
|
import net.corda.nodeapi.internal.serialization.AllWhitelist;
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.NotSerializableException;
|
import java.io.NotSerializableException;
|
||||||
|
|
||||||
|
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
|
||||||
public class ErrorMessageTests {
|
public class ErrorMessageTests {
|
||||||
private String errMsg(String property, String testname) {
|
private String errMsg(String property, String testname) {
|
||||||
return "Property '"
|
return "Property '"
|
||||||
@ -29,7 +31,12 @@ public class ErrorMessageTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJavaConstructorAnnotations() {
|
public void testJavaConstructorAnnotations() {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
|
||||||
SerializationOutput ser = new SerializationOutput(factory1);
|
SerializationOutput ser = new SerializationOutput(factory1);
|
||||||
|
|
||||||
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1)))
|
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1)))
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp;
|
||||||
|
|
||||||
|
import net.corda.core.serialization.SerializedBytes;
|
||||||
|
import net.corda.nodeapi.internal.serialization.AllWhitelist;
|
||||||
|
import org.junit.Test;
|
||||||
|
import java.io.NotSerializableException;
|
||||||
|
|
||||||
|
import static org.jgroups.util.Util.assertEquals;
|
||||||
|
|
||||||
|
public class JavaGenericsTest {
|
||||||
|
private static class Inner {
|
||||||
|
private final Integer v;
|
||||||
|
|
||||||
|
private Inner(Integer v) { this.v = v; }
|
||||||
|
public Integer getV() { return v; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class A<T> {
|
||||||
|
private final T t;
|
||||||
|
|
||||||
|
private A(T t) { this.t = t; }
|
||||||
|
public T getT() { return t; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void basicGeneric() throws NotSerializableException {
|
||||||
|
A a1 = new A(1);
|
||||||
|
|
||||||
|
SerializerFactory factory = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
new EvolutionSerializerGetter());
|
||||||
|
|
||||||
|
SerializationOutput ser = new SerializationOutput(factory);
|
||||||
|
SerializedBytes<?> bytes = ser.serialize(a1);
|
||||||
|
|
||||||
|
DeserializationInput des = new DeserializationInput(factory);
|
||||||
|
A a2 = des.deserialize(bytes, A.class);
|
||||||
|
|
||||||
|
assertEquals(1, a2.getT());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SerializedBytes<?> forceWildcardSerialize(A<?> a) throws NotSerializableException {
|
||||||
|
SerializerFactory factory = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
new EvolutionSerializerGetter());
|
||||||
|
|
||||||
|
return (new SerializationOutput(factory)).serialize(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SerializedBytes<?> forceWildcardSerializeFactory(
|
||||||
|
A<?> a,
|
||||||
|
SerializerFactory factory) throws NotSerializableException {
|
||||||
|
return (new SerializationOutput(factory)).serialize(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private A<?> forceWildcardDeserialize(SerializedBytes<?> bytes) throws NotSerializableException {
|
||||||
|
SerializerFactory factory = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
new EvolutionSerializerGetter());
|
||||||
|
|
||||||
|
DeserializationInput des = new DeserializationInput(factory);
|
||||||
|
return des.deserialize(bytes, A.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private A<?> forceWildcardDeserializeFactory(
|
||||||
|
SerializedBytes<?> bytes,
|
||||||
|
SerializerFactory factory) throws NotSerializableException {
|
||||||
|
return (new DeserializationInput(factory)).deserialize(bytes, A.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forceWildcard() throws NotSerializableException {
|
||||||
|
SerializedBytes<?> bytes = forceWildcardSerialize(new A(new Inner(29)));
|
||||||
|
Inner i = (Inner)forceWildcardDeserialize(bytes).getT();
|
||||||
|
assertEquals(29, i.getV());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forceWildcardSharedFactory() throws NotSerializableException {
|
||||||
|
SerializerFactory factory = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
new EvolutionSerializerGetter());
|
||||||
|
|
||||||
|
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory);
|
||||||
|
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();
|
||||||
|
|
||||||
|
assertEquals(29, i.getV());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp;
|
||||||
|
|
||||||
|
import net.corda.nodeapi.internal.serialization.AllWhitelist;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.NotSerializableException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class JavaPrivatePropertyTests {
|
||||||
|
static class C {
|
||||||
|
private String a;
|
||||||
|
|
||||||
|
C(String a) { this.a = a; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static class C2 {
|
||||||
|
private String a;
|
||||||
|
|
||||||
|
C2(String a) { this.a = a; }
|
||||||
|
|
||||||
|
public String getA() { return a; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||||
|
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerializerGetter);
|
||||||
|
SerializationOutput ser = new SerializationOutput(factory);
|
||||||
|
DeserializationInput des = new DeserializationInput(factory);
|
||||||
|
|
||||||
|
C c = new C("dripping taps");
|
||||||
|
C c2 = des.deserialize(ser.serialize(c), C.class);
|
||||||
|
|
||||||
|
assertEquals (c.a, c2.a);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now ensure we actually got a private property serializer
|
||||||
|
//
|
||||||
|
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
|
||||||
|
f.setAccessible(true);
|
||||||
|
|
||||||
|
ConcurrentHashMap<Object, AMQPSerializer<Object>> serializersByDescriptor =
|
||||||
|
(ConcurrentHashMap<Object, AMQPSerializer<Object>>) f.get(factory);
|
||||||
|
|
||||||
|
assertEquals(1, serializersByDescriptor.size());
|
||||||
|
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
|
||||||
|
assertEquals(1, cSerializer.getPropertySerializers().component1().size());
|
||||||
|
Object[] propertyReaders = cSerializer.getPropertySerializers().component1().toArray();
|
||||||
|
assertTrue (((PropertySerializer)propertyReaders[0]).getPropertyReader() instanceof PrivatePropertyReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singlePrivateWithConstructorAndGetter()
|
||||||
|
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||||
|
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(), evolutionSerializerGetter);
|
||||||
|
SerializationOutput ser = new SerializationOutput(factory);
|
||||||
|
DeserializationInput des = new DeserializationInput(factory);
|
||||||
|
|
||||||
|
C2 c = new C2("dripping taps");
|
||||||
|
C2 c2 = des.deserialize(ser.serialize(c), C2.class);
|
||||||
|
|
||||||
|
assertEquals (c.a, c2.a);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now ensure we actually got a private property serializer
|
||||||
|
//
|
||||||
|
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
|
||||||
|
f.setAccessible(true);
|
||||||
|
ConcurrentHashMap<Object, AMQPSerializer<Object>> serializersByDescriptor =
|
||||||
|
(ConcurrentHashMap<Object, AMQPSerializer<Object>>) f.get(factory);
|
||||||
|
|
||||||
|
assertEquals(1, serializersByDescriptor.size());
|
||||||
|
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
|
||||||
|
assertEquals(1, cSerializer.getPropertySerializers().component1().size());
|
||||||
|
Object[] propertyReaders = cSerializer.getPropertySerializers().component1().toArray();
|
||||||
|
assertTrue (((PropertySerializer)propertyReaders[0]).getPropertyReader() instanceof PublicPropertyReader);
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,9 @@ public class JavaSerialiseEnumTests {
|
|||||||
public void testJavaConstructorAnnotations() throws NotSerializableException {
|
public void testJavaConstructorAnnotations() throws NotSerializableException {
|
||||||
Bra bra = new Bra(Bras.UNDERWIRE);
|
Bra bra = new Bra(Bras.UNDERWIRE);
|
||||||
|
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
SerializationOutput ser = new SerializationOutput(factory1);
|
SerializationOutput ser = new SerializationOutput(factory1);
|
||||||
SerializedBytes<Object> bytes = ser.serialize(bra);
|
SerializedBytes<Object> bytes = ser.serialize(bra);
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,11 @@ public class JavaSerializationOutputTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Object serdes(Object obj) throws NotSerializableException {
|
private Object serdes(Object obj) throws NotSerializableException {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
SerializationOutput ser = new SerializationOutput(factory1);
|
SerializationOutput ser = new SerializationOutput(factory1);
|
||||||
SerializedBytes<Object> bytes = ser.serialize(obj);
|
SerializedBytes<Object> bytes = ser.serialize(obj);
|
||||||
|
|
||||||
|
@ -125,7 +125,9 @@ public class ListsSerializationJavaTest {
|
|||||||
|
|
||||||
// Have to have own version as Kotlin inline functions cannot be easily called from Java
|
// 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 {
|
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
SerializationOutput ser = new SerializationOutput(factory1);
|
SerializationOutput ser = new SerializationOutput(factory1);
|
||||||
SerializedBytes<Object> bytes = ser.serialize(container);
|
SerializedBytes<Object> bytes = ser.serialize(container);
|
||||||
DeserializationInput des = new DeserializationInput(factory1);
|
DeserializationInput des = new DeserializationInput(factory1);
|
||||||
|
@ -109,7 +109,12 @@ public class SetterConstructorTests {
|
|||||||
// despite having no constructor we should still be able to serialise an instance of C
|
// despite having no constructor we should still be able to serialise an instance of C
|
||||||
@Test
|
@Test
|
||||||
public void serialiseC() throws NotSerializableException {
|
public void serialiseC() throws NotSerializableException {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
|
||||||
SerializationOutput ser = new SerializationOutput(factory1);
|
SerializationOutput ser = new SerializationOutput(factory1);
|
||||||
|
|
||||||
C c1 = new C();
|
C c1 = new C();
|
||||||
@ -178,7 +183,11 @@ public class SetterConstructorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deserialiseC() throws NotSerializableException {
|
public void deserialiseC() throws NotSerializableException {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
|
||||||
C cPre1 = new C();
|
C cPre1 = new C();
|
||||||
|
|
||||||
@ -241,7 +250,11 @@ public class SetterConstructorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void serialiseOuterAndInner() throws NotSerializableException {
|
public void serialiseOuterAndInner() throws NotSerializableException {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
|
||||||
Inner1 i1 = new Inner1("Hello");
|
Inner1 i1 = new Inner1("Hello");
|
||||||
Inner2 i2 = new Inner2();
|
Inner2 i2 = new Inner2();
|
||||||
@ -263,7 +276,11 @@ public class SetterConstructorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typeMistmatch() throws NotSerializableException {
|
public void typeMistmatch() throws NotSerializableException {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
|
||||||
TypeMismatch tm = new TypeMismatch();
|
TypeMismatch tm = new TypeMismatch();
|
||||||
tm.setA(10);
|
tm.setA(10);
|
||||||
@ -279,7 +296,11 @@ public class SetterConstructorTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typeMistmatch2() throws NotSerializableException {
|
public void typeMistmatch2() throws NotSerializableException {
|
||||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||||
|
SerializerFactory factory1 = new SerializerFactory(
|
||||||
|
AllWhitelist.INSTANCE,
|
||||||
|
ClassLoader.getSystemClassLoader(),
|
||||||
|
evolutionSerialiserGetter);
|
||||||
|
|
||||||
TypeMismatch2 tm = new TypeMismatch2();
|
TypeMismatch2 tm = new TypeMismatch2();
|
||||||
tm.setA("10");
|
tm.setA("10");
|
||||||
|
@ -168,7 +168,7 @@ class X509UtilitiesTest {
|
|||||||
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
||||||
|
|
||||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||||
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
|
sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa)
|
||||||
|
|
||||||
// Load back server certificate
|
// Load back server certificate
|
||||||
val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
|
val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
|
||||||
@ -203,7 +203,7 @@ class X509UtilitiesTest {
|
|||||||
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
||||||
|
|
||||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||||
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
|
sslConfig.createDevKeyStores(MEGA_CORP.name, rootCa.certificate, intermediateCa)
|
||||||
sslConfig.createTrustStore(rootCa.certificate)
|
sslConfig.createTrustStore(rootCa.certificate)
|
||||||
|
|
||||||
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
|
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
import net.corda.core.serialization.SerializedBytes
|
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
|
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
|
||||||
import java.io.NotSerializableException
|
|
||||||
|
|
||||||
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
|
fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(),
|
||||||
|
EvolutionSerializerGetterTesting())
|
||||||
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
|
|
||||||
class TestSerializationOutput(
|
class TestSerializationOutput(
|
||||||
|
@ -11,13 +11,14 @@ class DeserializeAndReturnEnvelopeTests {
|
|||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||||
|
|
||||||
|
val factory = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun oneType() {
|
fun oneType() {
|
||||||
data class A(val a: Int, val b: String)
|
data class A(val a: Int, val b: String)
|
||||||
|
|
||||||
val a = A(10, "20")
|
val a = A(10, "20")
|
||||||
|
|
||||||
val factory = testDefaultFactory()
|
|
||||||
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
||||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
|
||||||
|
|
||||||
@ -33,7 +34,6 @@ class DeserializeAndReturnEnvelopeTests {
|
|||||||
|
|
||||||
val b = B(A(10, "20"), 30.0F)
|
val b = B(A(10, "20"), 30.0F)
|
||||||
|
|
||||||
val factory = testDefaultFactory()
|
|
||||||
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
||||||
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class DeserializeMapTests {
|
|||||||
private const val VERBOSE = false
|
private const val VERBOSE = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sf = testDefaultFactory()
|
private val sf = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun mapTest() {
|
fun mapTest() {
|
||||||
|
@ -18,7 +18,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
|
|||||||
//
|
//
|
||||||
// Setup the test
|
// Setup the test
|
||||||
//
|
//
|
||||||
val setupFactory = testDefaultFactory()
|
val setupFactory = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
|
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
|
||||||
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
|
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
|
||||||
@ -57,7 +57,7 @@ class DeserializeNeedingCarpentryOfEnumsTest : AmqpCarpenterBase(AllWhitelist) {
|
|||||||
//
|
//
|
||||||
// Setup the test
|
// Setup the test
|
||||||
//
|
//
|
||||||
val setupFactory = testDefaultFactory()
|
val setupFactory = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
|
val enumConstants = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF",
|
||||||
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
|
"GGG", "HHH", "III", "JJJ").associateBy({ it }, { EnumField() })
|
||||||
|
@ -17,8 +17,8 @@ class DeserializeNeedingCarpentrySimpleTypesTest : AmqpCarpenterBase(AllWhitelis
|
|||||||
private const val VERBOSE = false
|
private const val VERBOSE = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sf = testDefaultFactory()
|
private val sf = testDefaultFactoryNoEvolution()
|
||||||
private val sf2 = testDefaultFactory()
|
private val sf2 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun singleInt() {
|
fun singleInt() {
|
||||||
|
@ -27,7 +27,7 @@ class DeserializeNeedingCarpentryTests : AmqpCarpenterBase(AllWhitelist) {
|
|||||||
private const val VERBOSE = false
|
private const val VERBOSE = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sf1 = testDefaultFactory()
|
private val sf1 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
// Deserialize with whitelisting on to check that `CordaSerializable` annotation present.
|
// Deserialize with whitelisting on to check that `CordaSerializable` annotation present.
|
||||||
private val sf2 = testDefaultFactoryWithWhitelist()
|
private val sf2 = testDefaultFactoryWithWhitelist()
|
||||||
|
@ -16,8 +16,8 @@ class DeserializeSimpleTypesTests {
|
|||||||
private const val VERBOSE = false
|
private const val VERBOSE = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val sf1 = testDefaultFactory()
|
val sf1 = testDefaultFactoryNoEvolution()
|
||||||
val sf2 = testDefaultFactory()
|
val sf2 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChar() {
|
fun testChar() {
|
||||||
|
@ -6,6 +6,8 @@ import org.assertj.core.api.Assertions
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ -387,21 +389,25 @@ class EnumEvolvabilityTests {
|
|||||||
data class C1(val annotatedEnum: AnnotatedEnumOnce)
|
data class C1(val annotatedEnum: AnnotatedEnumOnce)
|
||||||
|
|
||||||
val sf = testDefaultFactory()
|
val sf = testDefaultFactory()
|
||||||
|
val f = sf.javaClass.getDeclaredField("transformsCache")
|
||||||
|
f.isAccessible = true
|
||||||
|
|
||||||
assertEquals(0, sf.transformsCache.size)
|
val transformsCache = f.get(sf) as ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>
|
||||||
|
|
||||||
|
assertEquals(0, transformsCache.size)
|
||||||
|
|
||||||
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
|
val sb1 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C1(AnnotatedEnumOnce.D))
|
||||||
|
|
||||||
assertEquals(2, sf.transformsCache.size)
|
assertEquals(2, transformsCache.size)
|
||||||
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
|
assertTrue(transformsCache.containsKey(C1::class.java.name))
|
||||||
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
||||||
|
|
||||||
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
|
val sb2 = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C2(AnnotatedEnumOnce.D))
|
||||||
|
|
||||||
assertEquals(3, sf.transformsCache.size)
|
assertEquals(3, transformsCache.size)
|
||||||
assertTrue(sf.transformsCache.containsKey(C1::class.java.name))
|
assertTrue(transformsCache.containsKey(C1::class.java.name))
|
||||||
assertTrue(sf.transformsCache.containsKey(C2::class.java.name))
|
assertTrue(transformsCache.containsKey(C2::class.java.name))
|
||||||
assertTrue(sf.transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
assertTrue(transformsCache.containsKey(AnnotatedEnumOnce::class.java.name))
|
||||||
|
|
||||||
assertEquals(sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
|
assertEquals(sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name],
|
||||||
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])
|
sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name])
|
||||||
|
@ -65,7 +65,7 @@ class EnumTests {
|
|||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||||
|
|
||||||
private val sf1 = testDefaultFactory()
|
private val sf1 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun serialiseSimpleTest() {
|
fun serialiseSimpleTest() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions
|
import org.assertj.core.api.Assertions
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
@ -12,6 +13,8 @@ class ErrorMessagesTests {
|
|||||||
private fun errMsg(property:String, testname: String) =
|
private fun errMsg(property:String, testname: String) =
|
||||||
"Property '$property' or its getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C"
|
"Property '$property' or its getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C"
|
||||||
|
|
||||||
|
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
|
||||||
|
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
|
||||||
@Test
|
@Test
|
||||||
fun privateProperty() {
|
fun privateProperty() {
|
||||||
data class C(private val a: Int)
|
data class C(private val a: Int)
|
||||||
@ -25,6 +28,8 @@ class ErrorMessagesTests {
|
|||||||
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname))
|
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
|
||||||
|
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
|
||||||
@Test
|
@Test
|
||||||
fun privateProperty2() {
|
fun privateProperty2() {
|
||||||
data class C(val a: Int, private val b: Int)
|
data class C(val a: Int, private val b: Int)
|
||||||
@ -38,6 +43,8 @@ class ErrorMessagesTests {
|
|||||||
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("b", testname))
|
}.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("b", testname))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
|
||||||
|
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
|
||||||
@Test
|
@Test
|
||||||
fun privateProperty3() {
|
fun privateProperty3() {
|
||||||
// despite b being private, the getter we've added is public and thus allows for the serialisation
|
// despite b being private, the getter we've added is public and thus allows for the serialisation
|
||||||
@ -54,6 +61,8 @@ class ErrorMessagesTests {
|
|||||||
val c = DeserializationInput(sf).deserialize(bytes)
|
val c = DeserializationInput(sf).deserialize(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Java allows this to be set at the class level yet Kotlin doesn't for some reason
|
||||||
|
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
|
||||||
@Test
|
@Test
|
||||||
fun protectedProperty() {
|
fun protectedProperty() {
|
||||||
data class C(protected val a: Int)
|
data class C(protected val a: Int)
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of [EvolutionSerializerGetterBase] that disables all evolution within a
|
||||||
|
* [SerializerFactory]. This is most useful in testing where it is known that evolution should not be
|
||||||
|
* occurring and where bugs may be hidden by transparent invocation of an [EvolutionSerializer]. This
|
||||||
|
* prevents that by simply throwing an exception whenever such a serializer is requested.
|
||||||
|
*/
|
||||||
|
class EvolutionSerializerGetterTesting : EvolutionSerializerGetterBase() {
|
||||||
|
override fun getEvolutionSerializer(factory: SerializerFactory,
|
||||||
|
typeNotation: TypeNotation,
|
||||||
|
newSerializer: AMQPSerializer<Any>,
|
||||||
|
schemas: SerializationSchemas): AMQPSerializer<Any> {
|
||||||
|
throw NotSerializableException("No evolution should be occurring\n" +
|
||||||
|
" ${typeNotation.name}\n" +
|
||||||
|
" ${typeNotation.descriptor.name}\n" +
|
||||||
|
" ${newSerializer.type.typeName}\n" +
|
||||||
|
" ${newSerializer.typeDescriptor}\n\n${schemas.schema}")
|
||||||
|
}
|
||||||
|
}
|
@ -2,26 +2,90 @@ package net.corda.nodeapi.internal.serialization.amqp
|
|||||||
|
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class GenericsTests {
|
class GenericsTests {
|
||||||
|
companion object {
|
||||||
|
val VERBOSE = false
|
||||||
|
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
var localPath = projectRootDir.toUri().resolve(
|
||||||
|
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit
|
||||||
|
|
||||||
|
private fun <T : Any> BytesAndSchemas<T>.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit
|
||||||
|
|
||||||
|
private fun ConcurrentHashMap<Any, AMQPSerializer<Any>>.printKeyToType() {
|
||||||
|
if (!VERBOSE) return
|
||||||
|
|
||||||
|
forEach {
|
||||||
|
println("Key = ${it.key} - ${it.value.type.typeName}")
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun twoDifferentTypesSameParameterizedOuter() {
|
||||||
|
data class G<A>(val a: A)
|
||||||
|
|
||||||
|
val factory = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
|
val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() }
|
||||||
|
|
||||||
|
factory.getSerializersByDescriptor().printKeyToType()
|
||||||
|
|
||||||
|
val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() }
|
||||||
|
|
||||||
|
factory.getSerializersByDescriptor().printKeyToType()
|
||||||
|
|
||||||
|
listOf(factory, testDefaultFactory()).forEach { f ->
|
||||||
|
DeserializationInput(f).deserialize(bytes1.obj).apply { assertEquals("hi", this.a) }
|
||||||
|
DeserializationInput(f).deserialize(bytes2.obj).apply { assertEquals(121, this.a) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doWeIgnoreMultipleParams() {
|
||||||
|
data class G1<out T>(val a: T)
|
||||||
|
data class G2<out T>(val a: T)
|
||||||
|
data class Wrapper<out T>(val a: Int, val b: G1<T>, val c: G2<T>)
|
||||||
|
|
||||||
|
val factory = testDefaultFactoryNoEvolution()
|
||||||
|
val factory2 = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
|
val bytes = SerializationOutput(factory).serializeAndReturnSchema(Wrapper(1, G1("hi"), G2("poop"))).apply { printSchema() }
|
||||||
|
printSeparator()
|
||||||
|
DeserializationInput(factory2).deserialize(bytes.obj)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nestedSerializationOfGenerics() {
|
fun nestedSerializationOfGenerics() {
|
||||||
data class G<T>(val a: T)
|
data class G<out T>(val a: T)
|
||||||
data class Wrapper<T>(val a: Int, val b: G<T>)
|
data class Wrapper<out T>(val a: Int, val b: G<T>)
|
||||||
|
|
||||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
val factory = testDefaultFactoryNoEvolution()
|
||||||
val altContextFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
val altContextFactory = testDefaultFactoryNoEvolution()
|
||||||
val ser = SerializationOutput(factory)
|
val ser = SerializationOutput(factory)
|
||||||
|
|
||||||
val bytes = ser.serializeAndReturnSchema(G("hi"))
|
val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() }
|
||||||
|
|
||||||
|
factory.getSerializersByDescriptor().printKeyToType()
|
||||||
|
|
||||||
assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a)
|
assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a)
|
||||||
assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a)
|
assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a)
|
||||||
|
|
||||||
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi")))
|
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))).apply { printSchema() }
|
||||||
|
|
||||||
|
factory.getSerializersByDescriptor().printKeyToType()
|
||||||
|
|
||||||
|
printSeparator()
|
||||||
|
|
||||||
DeserializationInput(factory).deserialize(bytes2.obj).apply {
|
DeserializationInput(factory).deserialize(bytes2.obj).apply {
|
||||||
assertEquals(1, a)
|
assertEquals(1, a)
|
||||||
@ -36,7 +100,7 @@ class GenericsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
|
fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
|
||||||
data class G(val a : Int)
|
data class G(val a: Int)
|
||||||
data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)
|
data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)
|
||||||
|
|
||||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
@ -71,27 +135,30 @@ class GenericsTests {
|
|||||||
ser.serialize(Wrapper(Container(InnerA(1)))).apply {
|
ser.serialize(Wrapper(Container(InnerA(1)))).apply {
|
||||||
factories.forEach {
|
factories.forEach {
|
||||||
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
|
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
|
||||||
|
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ser.serialize(Wrapper(Container(InnerB(1)))).apply {
|
ser.serialize(Wrapper(Container(InnerB(1)))).apply {
|
||||||
factories.forEach {
|
factories.forEach {
|
||||||
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
|
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
|
||||||
|
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply {
|
ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply {
|
||||||
factories.forEach {
|
factories.forEach {
|
||||||
DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) }
|
DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) }
|
||||||
|
it.getSerializersByDescriptor().printKeyToType(); printSeparator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
|
fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
|
||||||
data class Inner(val a : Int)
|
data class Inner(val a: Int)
|
||||||
data class Container<T : Any>(val b: Inner)
|
data class Container<T : Any>(val b: Inner)
|
||||||
data class Wrapper<T: Any>(val c: Container<T>)
|
data class Wrapper<T : Any>(val c: Container<T>)
|
||||||
|
|
||||||
val factorys = listOf(
|
val factorys = listOf(
|
||||||
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||||
@ -111,4 +178,103 @@ class GenericsTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
data class ForceWildcard<out T>(val t: T)
|
||||||
|
|
||||||
|
private fun forceWildcardSerialize(
|
||||||
|
a: ForceWildcard<*>,
|
||||||
|
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())): SerializedBytes<*> {
|
||||||
|
val bytes = SerializationOutput(factory).serializeAndReturnSchema(a)
|
||||||
|
factory.getSerializersByDescriptor().printKeyToType()
|
||||||
|
bytes.printSchema()
|
||||||
|
return bytes.obj
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun forceWildcardDeserializeString(
|
||||||
|
bytes: SerializedBytes<*>,
|
||||||
|
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
|
||||||
|
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<String>>)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun forceWildcardDeserializeDouble(
|
||||||
|
bytes: SerializedBytes<*>,
|
||||||
|
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
|
||||||
|
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<Double>>)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun forceWildcardDeserialize(
|
||||||
|
bytes: SerializedBytes<*>,
|
||||||
|
factory: SerializerFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) {
|
||||||
|
DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<*>>)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forceWildcard() {
|
||||||
|
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello")))
|
||||||
|
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forceWildcardSharedFactory() {
|
||||||
|
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
|
forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"), f), f)
|
||||||
|
forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0), f), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forceWildcardDeserialize() {
|
||||||
|
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello")))
|
||||||
|
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10)))
|
||||||
|
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forceWildcardDeserializeSharedFactory() {
|
||||||
|
val f = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
|
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"), f), f)
|
||||||
|
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10), f), f)
|
||||||
|
forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0), f), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadGenericFromFile() {
|
||||||
|
val resource = "${javaClass.simpleName}.${testName()}"
|
||||||
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
|
// Uncomment to re-generate test files, needs to be done in three stages
|
||||||
|
// File(URI("$localPath/$resource")).writeBytes(forceWildcardSerialize(ForceWildcard("wibble")).bytes)
|
||||||
|
|
||||||
|
assertEquals("wibble",
|
||||||
|
DeserializationInput(sf).deserialize(SerializedBytes<ForceWildcard<*>>(
|
||||||
|
File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DifferentBounds {
|
||||||
|
fun go()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun differentBounds() {
|
||||||
|
data class A (val a: Int): DifferentBounds {
|
||||||
|
override fun go() {
|
||||||
|
println(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class G<out T : DifferentBounds>(val b: T)
|
||||||
|
|
||||||
|
val factorys = listOf(
|
||||||
|
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||||
|
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
|
||||||
|
|
||||||
|
val ser = SerializationOutput(factorys[0])
|
||||||
|
|
||||||
|
ser.serialize(G(A(10))).apply {
|
||||||
|
factorys.forEach {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.apache.qpid.proton.amqp.Symbol
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class PrivatePropertyTests {
|
||||||
|
private val factory = testDefaultFactory()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePrivateProperty() {
|
||||||
|
data class C(private val b: String)
|
||||||
|
|
||||||
|
val c1 = C("Pants are comfortable sometimes")
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePrivatePropertyNullableNotNull() {
|
||||||
|
data class C(private val b: String?)
|
||||||
|
|
||||||
|
val c1 = C("Pants are comfortable sometimes")
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePrivatePropertyNullableNull() {
|
||||||
|
data class C(private val b: String?)
|
||||||
|
|
||||||
|
val c1 = C(null)
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePublicOnePrivateProperty() {
|
||||||
|
data class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
|
val c1 = C(1, 2)
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePublicOnePrivateProperty2() {
|
||||||
|
data class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
|
val c1 = C(1, 2)
|
||||||
|
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||||
|
assertEquals(1, schemaAndBlob.schema.types.size)
|
||||||
|
|
||||||
|
val field = SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")
|
||||||
|
field.isAccessible = true
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val serializersByDescriptor = field.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
|
||||||
|
|
||||||
|
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
|
||||||
|
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||||
|
assertEquals(1, this.size)
|
||||||
|
assertTrue(this.first() is ObjectSerializer)
|
||||||
|
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.getters.toList()
|
||||||
|
assertEquals(2, propertySerializers.size)
|
||||||
|
// a was public so should have a synthesised getter
|
||||||
|
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||||
|
|
||||||
|
// b is private and thus won't have teh getter so we'll have reverted
|
||||||
|
// to using reflection to remove the inaccessible property
|
||||||
|
assertTrue(propertySerializers[1].propertyReader is PrivatePropertyReader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetterMakesAPublicReader() {
|
||||||
|
data class C(val a: Int, private val b: Int) {
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
fun getB() = b
|
||||||
|
}
|
||||||
|
|
||||||
|
val c1 = C(1, 2)
|
||||||
|
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||||
|
assertEquals(1, schemaAndBlob.schema.types.size)
|
||||||
|
|
||||||
|
val field = SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")
|
||||||
|
field.isAccessible = true
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val serializersByDescriptor = field.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
|
||||||
|
|
||||||
|
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
|
||||||
|
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||||
|
assertEquals(1, this.size)
|
||||||
|
assertTrue(this.first() is ObjectSerializer)
|
||||||
|
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.getters.toList()
|
||||||
|
assertEquals(2, propertySerializers.size)
|
||||||
|
|
||||||
|
// as before, a is public so we'll use the getter method
|
||||||
|
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||||
|
|
||||||
|
// the getB() getter explicitly added means we should use the "normal" public
|
||||||
|
// method reader rather than the private oen
|
||||||
|
assertTrue(propertySerializers[1].propertyReader is PublicPropertyReader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNested() {
|
||||||
|
data class Inner(private val a: Int)
|
||||||
|
data class Outer(private val i: Inner)
|
||||||
|
|
||||||
|
val c1 = Outer(Inner(1010101))
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
}
|
@ -169,9 +169,11 @@ class SerializationOutputTests {
|
|||||||
|
|
||||||
private inline fun <reified T : Any> serdes(obj: T,
|
private inline fun <reified T : Any> serdes(obj: T,
|
||||||
factory: SerializerFactory = SerializerFactory(
|
factory: SerializerFactory = SerializerFactory(
|
||||||
AllWhitelist, ClassLoader.getSystemClassLoader()),
|
AllWhitelist, ClassLoader.getSystemClassLoader(),
|
||||||
|
EvolutionSerializerGetterTesting()),
|
||||||
freshDeserializationFactory: SerializerFactory = SerializerFactory(
|
freshDeserializationFactory: SerializerFactory = SerializerFactory(
|
||||||
AllWhitelist, ClassLoader.getSystemClassLoader()),
|
AllWhitelist, ClassLoader.getSystemClassLoader(),
|
||||||
|
EvolutionSerializerGetterTesting()),
|
||||||
expectedEqual: Boolean = true,
|
expectedEqual: Boolean = true,
|
||||||
expectDeserializedEqual: Boolean = true): T {
|
expectDeserializedEqual: Boolean = true): T {
|
||||||
val ser = SerializationOutput(factory)
|
val ser = SerializationOutput(factory)
|
||||||
|
@ -10,7 +10,7 @@ class SerializeAndReturnSchemaTest {
|
|||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
inline private fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||||
|
|
||||||
val factory = testDefaultFactory()
|
val factory = testDefaultFactoryNoEvolution()
|
||||||
|
|
||||||
// just a simple test to verify the internal test extension for serialize does
|
// just a simple test to verify the internal test extension for serialize does
|
||||||
// indeed give us the correct schema back. This is more useful in support of other
|
// indeed give us the correct schema back. This is more useful in support of other
|
||||||
|
@ -45,7 +45,7 @@ class StaticInitialisationOfSerializedObjectTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun KotlinObjectWithCompanionObject() {
|
fun kotlinObjectWithCompanionObject() {
|
||||||
data class D(val c: C)
|
data class D(val c: C)
|
||||||
|
|
||||||
val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
val sf = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
@ -104,7 +104,7 @@ class StaticInitialisationOfSerializedObjectTest {
|
|||||||
override val classCarpenter = ClassCarpenter(ClassLoader.getSystemClassLoader(), wl2)
|
override val classCarpenter = ClassCarpenter(ClassLoader.getSystemClassLoader(), wl2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This time have the serilization factory and the carpenter use different whitelists
|
// This time have the serialization factory and the carpenter use different whitelists
|
||||||
@Test
|
@Test
|
||||||
fun deserializeTest2() {
|
fun deserializeTest2() {
|
||||||
data class D(val c: C2)
|
data class D(val c: C2)
|
||||||
|
@ -35,9 +35,16 @@ fun Schema.mangleNames(names: List<String>): Schema {
|
|||||||
return Schema(types = newTypes)
|
return Schema(types = newTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom implementation of a [SerializerFactory] where we need to give it a class carpenter
|
||||||
|
* rather than have it create its own
|
||||||
|
*/
|
||||||
|
class SerializerFactoryExternalCarpenter(override val classCarpenter: ClassCarpenter)
|
||||||
|
: SerializerFactory (classCarpenter.whitelist, classCarpenter.classloader)
|
||||||
|
|
||||||
open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
||||||
var cc = ClassCarpenter(whitelist = whitelist)
|
var cc = ClassCarpenter(whitelist = whitelist)
|
||||||
var factory = SerializerFactory(AllWhitelist, cc.classloader)
|
var factory = SerializerFactoryExternalCarpenter(cc)
|
||||||
|
|
||||||
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
||||||
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
||||||
|
Binary file not shown.
@ -102,21 +102,21 @@ class AuthDBTests : NodeBasedTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `login with correct credentials`() {
|
fun `login with correct credentials`() {
|
||||||
client.start("user", "foo")
|
client.start("user", "foo").close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `login with wrong credentials`() {
|
fun `login with wrong credentials`() {
|
||||||
client.start("user", "foo")
|
client.start("user", "foo").close()
|
||||||
assertFailsWith(
|
assertFailsWith(
|
||||||
ActiveMQSecurityException::class,
|
ActiveMQSecurityException::class,
|
||||||
"Login with incorrect password should fail") {
|
"Login with incorrect password should fail") {
|
||||||
client.start("user", "bar")
|
client.start("user", "bar").close()
|
||||||
}
|
}
|
||||||
assertFailsWith(
|
assertFailsWith(
|
||||||
ActiveMQSecurityException::class,
|
ActiveMQSecurityException::class,
|
||||||
"Login with unknown username should fail") {
|
"Login with unknown username should fail") {
|
||||||
client.start("X", "foo")
|
client.start("X", "foo").close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ class AuthDBTests : NodeBasedTest() {
|
|||||||
assertFailsWith(
|
assertFailsWith(
|
||||||
ActiveMQSecurityException::class,
|
ActiveMQSecurityException::class,
|
||||||
"Login with incorrect password should fail") {
|
"Login with incorrect password should fail") {
|
||||||
client.start("user2", "bar")
|
client.start("user2", "bar").close()
|
||||||
}
|
}
|
||||||
|
|
||||||
db.insert(UserAndRoles(
|
db.insert(UserAndRoles(
|
||||||
@ -166,7 +166,7 @@ class AuthDBTests : NodeBasedTest() {
|
|||||||
password = encodePassword("bar"),
|
password = encodePassword("bar"),
|
||||||
roles = listOf("default")))
|
roles = listOf("default")))
|
||||||
|
|
||||||
client.start("user2", "bar")
|
client.start("user2", "bar").close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -22,6 +22,7 @@ import net.corda.testing.node.ClusterSpec
|
|||||||
import net.corda.testing.node.NotarySpec
|
import net.corda.testing.node.NotarySpec
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
import org.junit.ClassRule
|
import org.junit.ClassRule
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -35,6 +36,7 @@ class RaftNotaryServiceTests : IntegrationTest() {
|
|||||||
}
|
}
|
||||||
private val notaryName = CordaX500Name("RAFT Notary Service", "London", "GB")
|
private val notaryName = CordaX500Name("RAFT Notary Service", "London", "GB")
|
||||||
|
|
||||||
|
@Ignore("Test has undeterministic capacity to hang, ignore till fixed")
|
||||||
@Test
|
@Test
|
||||||
fun `detect double spend`() {
|
fun `detect double spend`() {
|
||||||
driver(
|
driver(
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
import net.corda.core.crypto.SignedData
|
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
import net.corda.core.internal.div
|
|
||||||
import net.corda.core.internal.exists
|
|
||||||
import net.corda.core.internal.list
|
|
||||||
import net.corda.core.internal.readAll
|
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
@ -75,7 +71,7 @@ class NetworkMapTest : IntegrationTest() {
|
|||||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||||
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
||||||
.readAll()
|
.readAll()
|
||||||
.deserialize<SignedData<NetworkParameters>>()
|
.deserialize<SignedDataWithCert<NetworkParameters>>()
|
||||||
.verified()
|
.verified()
|
||||||
// We use a random modified time above to make the network parameters unqiue so that we're sure they came
|
// We use a random modified time above to make the network parameters unqiue so that we're sure they came
|
||||||
// from the server
|
// from the server
|
||||||
|
@ -67,7 +67,11 @@ class NodeRegistrationTest : IntegrationTest() {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun startServer() {
|
fun startServer() {
|
||||||
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), DEV_ROOT_CA, "localhost", registrationHandler)
|
server = NetworkMapServer(
|
||||||
|
cacheTimeout = 1.minutes,
|
||||||
|
hostAndPort = portAllocation.nextHostAndPort(),
|
||||||
|
myHostNameValue = "localhost",
|
||||||
|
additionalServices = registrationHandler)
|
||||||
serverHostAndPort = server.start()
|
serverHostAndPort = server.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,15 @@ package net.corda.services.messaging
|
|||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.copyTo
|
||||||
|
import net.corda.core.internal.createDirectories
|
||||||
|
import net.corda.core.internal.exists
|
||||||
|
import net.corda.core.internal.x500Name
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||||
|
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
||||||
@ -90,20 +95,13 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
|||||||
javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
val caKeyStore = loadKeyStore(
|
|
||||||
javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"),
|
|
||||||
"cordacadevpass")
|
|
||||||
|
|
||||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
|
||||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
|
||||||
|
|
||||||
val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
// Set name constrain to the legal name.
|
// Set name constrain to the legal name.
|
||||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
|
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
|
||||||
val clientCACert = X509Utilities.createCertificate(
|
val clientCACert = X509Utilities.createCertificate(
|
||||||
CertificateType.INTERMEDIATE_CA,
|
CertificateType.INTERMEDIATE_CA,
|
||||||
intermediateCA.certificate,
|
DEV_INTERMEDIATE_CA.certificate,
|
||||||
intermediateCA.keyPair,
|
DEV_INTERMEDIATE_CA.keyPair,
|
||||||
legalName.x500Principal,
|
legalName.x500Principal,
|
||||||
clientKeyPair.public,
|
clientKeyPair.public,
|
||||||
nameConstraints = nameConstraints)
|
nameConstraints = nameConstraints)
|
||||||
@ -123,7 +121,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
|||||||
X509Utilities.CORDA_CLIENT_CA,
|
X509Utilities.CORDA_CLIENT_CA,
|
||||||
clientKeyPair.private,
|
clientKeyPair.private,
|
||||||
keyPass,
|
keyPass,
|
||||||
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
|
arrayOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||||
|
|
||||||
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||||
@ -131,7 +129,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
|||||||
X509Utilities.CORDA_CLIENT_TLS,
|
X509Utilities.CORDA_CLIENT_TLS,
|
||||||
tlsKeyPair.private,
|
tlsKeyPair.private,
|
||||||
keyPass,
|
keyPass,
|
||||||
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
|
arrayOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
|
|||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||||
|
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
|
||||||
import net.corda.nodeapi.internal.persistence.*
|
import net.corda.nodeapi.internal.persistence.*
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
|
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
|
||||||
@ -223,7 +224,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
val identityService = makeIdentityService(identity.certificate)
|
val identityService = makeIdentityService(identity.certificate)
|
||||||
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
|
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
|
||||||
retrieveNetworkParameters()
|
retrieveNetworkParameters(identityService.trustRoot)
|
||||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
||||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
|
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
|
||||||
@ -665,23 +666,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
return PersistentKeyManagementService(identityService, keyPairs)
|
return PersistentKeyManagementService(identityService, keyPairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun retrieveNetworkParameters() {
|
private fun retrieveNetworkParameters(trustRoot: X509Certificate) {
|
||||||
val networkParamsFile = configuration.baseDirectory.list { paths ->
|
val networkParamsFile = configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME
|
||||||
paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().orElse(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
networkParameters = if (networkParamsFile != null) {
|
networkParameters = if (networkParamsFile.exists()) {
|
||||||
networkParamsFile.readAll().deserialize<SignedData<NetworkParameters>>().verified()
|
networkParamsFile.readAll().deserialize<SignedDataWithCert<NetworkParameters>>().verifiedNetworkMapCert(trustRoot)
|
||||||
} else {
|
} else {
|
||||||
log.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
|
log.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
|
||||||
val networkMapClient = checkNotNull(networkMapClient) {
|
val networkMapClient = checkNotNull(networkMapClient) {
|
||||||
"Node hasn't been configured to connect to a network map from which to get the network parameters"
|
"Node hasn't been configured to connect to a network map from which to get the network parameters"
|
||||||
}
|
}
|
||||||
val (networkMap, _) = networkMapClient.getNetworkMap()
|
val (networkMap, _) = networkMapClient.getNetworkMap()
|
||||||
val signedParams = checkNotNull(networkMapClient.getNetworkParameter(networkMap.networkParameterHash)) {
|
val signedParams = networkMapClient.getNetworkParameters(networkMap.networkParameterHash)
|
||||||
"Failed loading network parameters from network map server"
|
val verifiedParams = signedParams.verifiedNetworkMapCert(trustRoot)
|
||||||
}
|
|
||||||
val verifiedParams = signedParams.verified()
|
|
||||||
signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
|
||||||
verifiedParams
|
verifiedParams
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package net.corda.node.internal
|
|
||||||
|
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
|
||||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
|
||||||
import kotlin.reflect.KVisibility
|
|
||||||
import kotlin.reflect.full.declaredMemberFunctions
|
|
||||||
|
|
||||||
object DefaultCordaRpcPermissions {
|
|
||||||
|
|
||||||
private val invokePermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.associate { it.name to setOf(invokeRpc(it), all()) }
|
|
||||||
private val startFlowPermissions = setOf("startFlow", "startFlowDynamic", "startTrackedFlow", "startTrackedFlowDynamic").associate { it to this::startFlowPermission }
|
|
||||||
|
|
||||||
fun permissionsAllowing(methodName: String, args: List<Any?>): Set<String> {
|
|
||||||
|
|
||||||
val invoke = invokePermissions[methodName] ?: emptySet()
|
|
||||||
val start = startFlowPermissions[methodName]?.invoke(args)
|
|
||||||
return if (start != null) invoke + start else invoke
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun startFlowPermission(args: List<Any?>): String = if (args[0] is Class<*>) startFlow(args[0] as Class<FlowLogic<*>>) else startFlow(args[0] as String)
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ import java.io.InputStream
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
// TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140
|
// TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140
|
||||||
class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcAuthContext, private val permissionsAllowing: (methodName: String, args: List<Any?>) -> Set<String>) : CordaRPCOps {
|
class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcAuthContext) : CordaRPCOps {
|
||||||
|
|
||||||
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash = guard("uploadAttachmentWithMetadata") {
|
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash = guard("uploadAttachmentWithMetadata") {
|
||||||
implementation.uploadAttachmentWithMetadata(jar, uploader, filename)
|
implementation.uploadAttachmentWithMetadata(jar, uploader, filename)
|
||||||
|
@ -14,7 +14,7 @@ class SecureCordaRPCOps(services: ServiceHubInternal,
|
|||||||
smm: StateMachineManager,
|
smm: StateMachineManager,
|
||||||
database: CordaPersistence,
|
database: CordaPersistence,
|
||||||
flowStarter: FlowStarter,
|
flowStarter: FlowStarter,
|
||||||
val unsafe: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter)) : CordaRPCOps by RpcAuthorisationProxy(unsafe, ::rpcContext, DefaultCordaRpcPermissions::permissionsAllowing) {
|
val unsafe: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter)) : CordaRPCOps by RpcAuthorisationProxy(unsafe, ::rpcContext) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
|
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
|
||||||
|
@ -103,7 +103,7 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Provide a representation of RPC permissions based on Apache Shiro permissions framework.
|
* Provide a representation of RPC permissions based on Apache Shiro permissions framework.
|
||||||
* A permission represents a set of actions: for example, the set of all RPC invocations, or the set
|
* A permission represents a set of actions: for example, the set of all RPC invocations, or the set
|
||||||
* of RPC invocations acting on a given class of Flows in input. A permission `implies` another one if
|
* of RPC invocations acting on a given class of Flows in input. A permission `implies` another one if
|
||||||
@ -128,7 +128,7 @@ private class RPCPermission : DomainPermission {
|
|||||||
constructor() : super()
|
constructor() : super()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* A [org.apache.shiro.authz.permission.PermissionResolver] implementation for RPC permissions.
|
* A [org.apache.shiro.authz.permission.PermissionResolver] implementation for RPC permissions.
|
||||||
* Provides a method to construct an [RPCPermission] instance from its string representation
|
* Provides a method to construct an [RPCPermission] instance from its string representation
|
||||||
* in the form used by a Node admin.
|
* in the form used by a Node admin.
|
||||||
@ -141,7 +141,6 @@ private class RPCPermission : DomainPermission {
|
|||||||
*
|
*
|
||||||
* - `StartFlow.$FlowClassName`: allowing to call a `startFlow*` RPC method targeting a Flow instance
|
* - `StartFlow.$FlowClassName`: allowing to call a `startFlow*` RPC method targeting a Flow instance
|
||||||
* of a given class
|
* of a given class
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private object RPCPermissionResolver : PermissionResolver {
|
private object RPCPermissionResolver : PermissionResolver {
|
||||||
|
|
||||||
@ -253,7 +252,7 @@ private class NodeJdbcRealm(config: SecurityConfiguration.AuthService.DataSource
|
|||||||
|
|
||||||
private typealias ShiroCache<K, V> = org.apache.shiro.cache.Cache<K, V>
|
private typealias ShiroCache<K, V> = org.apache.shiro.cache.Cache<K, V>
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Adapts a [com.google.common.cache.Cache] to a [org.apache.shiro.cache.Cache] implementation.
|
* Adapts a [com.google.common.cache.Cache] to a [org.apache.shiro.cache.Cache] implementation.
|
||||||
*/
|
*/
|
||||||
private fun <K, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<K, V> {
|
private fun <K, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<K, V> {
|
||||||
@ -285,7 +284,7 @@ private fun <K, V> Cache<K, V>.toShiroCache(name: String) = object : ShiroCache<
|
|||||||
override fun toString() = "Guava cache adapter [$impl]"
|
override fun toString() = "Guava cache adapter [$impl]"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Implementation of [org.apache.shiro.cache.CacheManager] based on
|
* Implementation of [org.apache.shiro.cache.CacheManager] based on
|
||||||
* cache implementation in [com.google.common.cache]
|
* cache implementation in [com.google.common.cache]
|
||||||
*/
|
*/
|
||||||
|
@ -71,10 +71,7 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
|||||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||||
}
|
}
|
||||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
createDevKeyStores(myLegalName)
|
||||||
val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
|
||||||
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
|
||||||
createDevKeyStores(rootCert, intermediateCa, myLegalName)
|
|
||||||
|
|
||||||
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
|
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
|
||||||
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
|
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
|
||||||
|
@ -2,26 +2,26 @@ package net.corda.node.services.network
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.SignedData
|
import net.corda.core.internal.SignedDataWithCert
|
||||||
|
import net.corda.core.internal.checkOkResponse
|
||||||
import net.corda.core.internal.openHttpConnection
|
import net.corda.core.internal.openHttpConnection
|
||||||
|
import net.corda.core.internal.responseAs
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.utilities.NamedThreadFactory
|
import net.corda.node.utilities.NamedThreadFactory
|
||||||
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.network.NetworkMap
|
import net.corda.nodeapi.internal.network.NetworkMap
|
||||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||||
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@ -33,47 +33,34 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C
|
|||||||
|
|
||||||
fun publish(signedNodeInfo: SignedNodeInfo) {
|
fun publish(signedNodeInfo: SignedNodeInfo) {
|
||||||
val publishURL = URL("$networkMapUrl/publish")
|
val publishURL = URL("$networkMapUrl/publish")
|
||||||
val conn = publishURL.openHttpConnection()
|
publishURL.openHttpConnection().apply {
|
||||||
conn.doOutput = true
|
doOutput = true
|
||||||
conn.requestMethod = "POST"
|
requestMethod = "POST"
|
||||||
conn.setRequestProperty("Content-Type", "application/octet-stream")
|
setRequestProperty("Content-Type", "application/octet-stream")
|
||||||
conn.outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
|
outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
|
||||||
|
checkOkResponse()
|
||||||
// This will throw IOException if the response code is not HTTP 200.
|
}
|
||||||
// This gives a much better exception then reading the error stream.
|
|
||||||
conn.inputStream.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNetworkMap(): NetworkMapResponse {
|
fun getNetworkMap(): NetworkMapResponse {
|
||||||
val conn = networkMapUrl.openHttpConnection()
|
val connection = networkMapUrl.openHttpConnection()
|
||||||
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize<SignedNetworkMap>()
|
val signedNetworkMap = connection.responseAs<SignedDataWithCert<NetworkMap>>()
|
||||||
val networkMap = signedNetworkMap.verified(trustedRoot)
|
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
|
||||||
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
val timeout = CacheControl.parse(Headers.of(connection.headerFields.filterKeys { it != null }.mapValues { it.value[0] })).maxAgeSeconds().seconds
|
||||||
return NetworkMapResponse(networkMap, timeout)
|
return NetworkMapResponse(networkMap, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo {
|
||||||
val conn = URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection()
|
return URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection().responseAs<SignedNodeInfo>().verified()
|
||||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize<SignedNodeInfo>()
|
|
||||||
signedNodeInfo.verified()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNetworkParameter(networkParameterHash: SecureHash): SignedData<NetworkParameters>? {
|
fun getNetworkParameters(networkParameterHash: SecureHash): SignedDataWithCert<NetworkParameters> {
|
||||||
val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection()
|
return URL("$networkMapUrl/network-parameters/$networkParameterHash").openHttpConnection().responseAs()
|
||||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
conn.inputStream.use { it.readBytes() }.deserialize()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun myPublicHostname(): String {
|
fun myPublicHostname(): String {
|
||||||
val conn = URL("$networkMapUrl/my-hostname").openHttpConnection()
|
val connection = URL("$networkMapUrl/my-hostname").openHttpConnection()
|
||||||
return conn.inputStream.bufferedReader().use(BufferedReader::readLine)
|
return connection.inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +24,7 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr
|
|||||||
@Throws(CertificateRequestException::class)
|
@Throws(CertificateRequestException::class)
|
||||||
override fun retrieveCertificates(requestId: String): Array<Certificate>? {
|
override fun retrieveCertificates(requestId: String): Array<Certificate>? {
|
||||||
// Poll server to download the signed certificate once request has been approved.
|
// Poll server to download the signed certificate once request has been approved.
|
||||||
val url = URL("$registrationURL/$requestId")
|
val conn = URL("$registrationURL/$requestId").openHttpConnection()
|
||||||
val conn = url.openConnection() as HttpURLConnection
|
|
||||||
conn.requestMethod = "GET"
|
conn.requestMethod = "GET"
|
||||||
|
|
||||||
return when (conn.responseCode) {
|
return when (conn.responseCode) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
@ -9,16 +8,19 @@ import net.corda.testing.BOB_NAME
|
|||||||
import net.corda.testing.DEV_ROOT_CA
|
import net.corda.testing.DEV_ROOT_CA
|
||||||
import net.corda.testing.SerializationEnvironmentRule
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.driver.PortAllocation
|
import net.corda.testing.driver.PortAllocation
|
||||||
|
import net.corda.testing.internal.TestNodeInfoBuilder
|
||||||
import net.corda.testing.internal.createNodeInfoAndSigned
|
import net.corda.testing.internal.createNodeInfoAndSigned
|
||||||
|
import net.corda.testing.internal.signWith
|
||||||
import net.corda.testing.node.internal.network.NetworkMapServer
|
import net.corda.testing.node.internal.network.NetworkMapServer
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
|
|
||||||
class NetworkMapClientTest {
|
class NetworkMapClientTest {
|
||||||
@Rule
|
@Rule
|
||||||
@ -63,12 +65,24 @@ class NetworkMapClientTest {
|
|||||||
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `errors return a meaningful error message`() {
|
||||||
|
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||||
|
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||||
|
nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||||
|
val nodeInfo3 = nodeInfoBuilder.build()
|
||||||
|
val signedNodeInfo3 = nodeInfo3.signWith(listOf(aliceKey))
|
||||||
|
|
||||||
|
assertThatThrownBy { networkMapClient.publish(signedNodeInfo3) }
|
||||||
|
.isInstanceOf(IOException::class.java)
|
||||||
|
.hasMessage("Response Code 403: Missing signatures. Found 1 expected 2")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `download NetworkParameter correctly`() {
|
fun `download NetworkParameter correctly`() {
|
||||||
// The test server returns same network parameter for any hash.
|
// The test server returns same network parameter for any hash.
|
||||||
val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())?.verified()
|
val parametersHash = server.networkParameters.serialize().hash
|
||||||
assertNotNull(networkParameter)
|
val networkParameter = networkMapClient.getNetworkParameters(parametersHash).verified()
|
||||||
assertEquals(server.networkParameters, networkParameter)
|
assertEquals(server.networkParameters, networkParameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +161,8 @@ data class JmxPolicy(val startJmxHttpServer: Boolean = false,
|
|||||||
* @param useTestClock If true the test clock will be used in Node.
|
* @param useTestClock If true the test clock will be used in Node.
|
||||||
* @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or
|
* @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or
|
||||||
* not. Note that this may be overridden in [DriverDSL.startNode].
|
* not. Note that this may be overridden in [DriverDSL.startNode].
|
||||||
|
* @param waitForAllNodesToFinish If true, the nodes will not shut down automatically after executing the code in the driver DSL block.
|
||||||
|
* It will wait for them to be shut down externally instead.
|
||||||
* @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be
|
* @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be
|
||||||
* available from [DriverDSL.notaryHandles]. Defaults to a simple validating notary.
|
* available from [DriverDSL.notaryHandles]. Defaults to a simple validating notary.
|
||||||
* @param jmxPolicy Used to specify whether to expose JMX metrics via Jolokia HHTP/JSON. Defines two attributes:
|
* @param jmxPolicy Used to specify whether to expose JMX metrics via Jolokia HHTP/JSON. Defines two attributes:
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
package net.corda.testing.node.internal.network
|
package net.corda.testing.node.internal.network
|
||||||
|
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.internal.signWithCert
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
|
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import net.corda.nodeapi.internal.network.DigitalSignatureWithCert
|
|
||||||
import net.corda.nodeapi.internal.network.NetworkMap
|
import net.corda.nodeapi.internal.network.NetworkMap
|
||||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||||
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
|
||||||
import net.corda.testing.DEV_ROOT_CA
|
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
import org.eclipse.jetty.server.ServerConnector
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||||
@ -25,46 +21,31 @@ import org.glassfish.jersey.servlet.ServletContainer
|
|||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
import java.security.SignatureException
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.security.auth.x500.X500Principal
|
|
||||||
import javax.ws.rs.*
|
import javax.ws.rs.*
|
||||||
import javax.ws.rs.core.MediaType
|
import javax.ws.rs.core.MediaType
|
||||||
import javax.ws.rs.core.Response
|
import javax.ws.rs.core.Response
|
||||||
import javax.ws.rs.core.Response.ok
|
import javax.ws.rs.core.Response.ok
|
||||||
|
import javax.ws.rs.core.Response.status
|
||||||
|
|
||||||
class NetworkMapServer(cacheTimeout: Duration,
|
class NetworkMapServer(private val cacheTimeout: Duration,
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
rootCa: CertificateAndKeyPair = DEV_ROOT_CA,
|
private val networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
|
||||||
private val myHostNameValue: String = "test.host.name",
|
private val myHostNameValue: String = "test.host.name",
|
||||||
vararg additionalServices: Any) : Closeable {
|
vararg additionalServices: Any) : Closeable {
|
||||||
companion object {
|
companion object {
|
||||||
private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10)
|
private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, 40000, Instant.now(), 10)
|
||||||
|
|
||||||
private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair {
|
|
||||||
val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
|
||||||
val networkMapCert = X509Utilities.createCertificate(
|
|
||||||
CertificateType.NETWORK_MAP,
|
|
||||||
rootCAKeyAndCert.certificate,
|
|
||||||
rootCAKeyAndCert.keyPair,
|
|
||||||
X500Principal("CN=Corda Network Map,O=R3 Ltd,L=London,C=GB"),
|
|
||||||
networkMapKey.public)
|
|
||||||
// Check that the certificate validates. Nodes will perform this check upon receiving a network map,
|
|
||||||
// it's better to fail here than there.
|
|
||||||
X509Utilities.validateCertificateChain(rootCAKeyAndCert.certificate, networkMapCert)
|
|
||||||
return CertificateAndKeyPair(networkMapCert, networkMapKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val server: Server
|
private val server: Server
|
||||||
var networkParameters: NetworkParameters = stubNetworkParameters
|
var networkParameters: NetworkParameters = stubNetworkParameters
|
||||||
set(networkParameters) {
|
set(networkParameters) {
|
||||||
check(field == stubNetworkParameters) { "Network parameters can be set only once" }
|
check(field == stubNetworkParameters) { "Network parameters can be set only once" }
|
||||||
field = networkParameters
|
field = networkParameters
|
||||||
}
|
}
|
||||||
private val serializedParameters get() = networkParameters.serialize()
|
private val service = InMemoryNetworkMapService()
|
||||||
private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(rootCa))
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
||||||
@ -104,32 +85,34 @@ class NetworkMapServer(cacheTimeout: Duration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Path("network-map")
|
@Path("network-map")
|
||||||
inner class InMemoryNetworkMapService(private val cacheTimeout: Duration,
|
inner class InMemoryNetworkMapService {
|
||||||
private val networkMapKeyAndCert: CertificateAndKeyPair) {
|
|
||||||
private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
|
private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
|
||||||
private val parametersHash by lazy { serializedParameters.hash }
|
private val signedNetParams by lazy {
|
||||||
private val signedParameters by lazy { SignedData(
|
networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
|
||||||
serializedParameters,
|
}
|
||||||
DigitalSignature.WithKey(networkMapKeyAndCert.keyPair.public, Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedParameters.bytes))) }
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("publish")
|
@Path("publish")
|
||||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
fun publishNodeInfo(input: InputStream): Response {
|
fun publishNodeInfo(input: InputStream): Response {
|
||||||
val registrationData = input.readBytes().deserialize<SignedNodeInfo>()
|
return try {
|
||||||
val nodeInfo = registrationData.verified()
|
val signedNodeInfo = input.readBytes().deserialize<SignedNodeInfo>()
|
||||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
signedNodeInfo.verified()
|
||||||
nodeInfoMap.put(nodeInfoHash, registrationData)
|
nodeInfoMap[signedNodeInfo.raw.hash] = signedNodeInfo
|
||||||
return ok().build()
|
ok()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
when (e) {
|
||||||
|
is SignatureException -> status(Response.Status.FORBIDDEN).entity(e.message)
|
||||||
|
else -> status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.message)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
fun getNetworkMap(): Response {
|
fun getNetworkMap(): Response {
|
||||||
val networkMap = NetworkMap(nodeInfoMap.keys.toList(), parametersHash)
|
val networkMap = NetworkMap(nodeInfoMap.keys.toList(), signedNetParams.raw.hash)
|
||||||
val serializedNetworkMap = networkMap.serialize()
|
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
|
||||||
val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes)
|
|
||||||
val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate, signature))
|
|
||||||
return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build()
|
return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,16 +134,15 @@ class NetworkMapServer(cacheTimeout: Duration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("network-parameter/{var}")
|
@Path("network-parameters/{var}")
|
||||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
fun getNetworkParameter(@PathParam("var") networkParameterHash: String): Response {
|
fun getNetworkParameter(@PathParam("var") hash: String): Response {
|
||||||
return Response.ok(signedParameters.serialize().bytes).build()
|
require(signedNetParams.raw.hash == SecureHash.parse(hash))
|
||||||
|
return Response.ok(signedNetParams.serialize().bytes).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("my-hostname")
|
@Path("my-hostname")
|
||||||
fun getHostName(): Response {
|
fun getHostName(): Response = Response.ok(myHostNameValue).build()
|
||||||
return Response.ok(myHostNameValue).build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,6 @@ import net.corda.core.contracts.TypeOnlyCommandData
|
|||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import net.corda.nodeapi.internal.crypto.getCertificateAndKeyPair
|
|
||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@ -32,17 +29,10 @@ val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES")
|
|||||||
val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT")
|
val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT")
|
||||||
@JvmField
|
@JvmField
|
||||||
val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR")
|
val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR")
|
||||||
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy {
|
|
||||||
// TODO: Should be identity scheme
|
|
||||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
|
||||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
|
||||||
}
|
|
||||||
|
|
||||||
val DEV_ROOT_CA: CertificateAndKeyPair by lazy {
|
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA }
|
||||||
// TODO: Should be identity scheme
|
|
||||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
val DEV_ROOT_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_ROOT_CA }
|
||||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command<TypeOnlyCommandData>(DummyCommandData, signers.toList())
|
fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command<TypeOnlyCommandData>(DummyCommandData, signers.toList())
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ object DummyDealStateSchemaV1 : MappedSchema(schemaFamily = DummyDealStateSchema
|
|||||||
@Entity
|
@Entity
|
||||||
@Table(name = "dummy_deal_states")
|
@Table(name = "dummy_deal_states")
|
||||||
class PersistentDummyDealState(
|
class PersistentDummyDealState(
|
||||||
|
/** parent attributes */
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@Column(name = "participants")
|
@Column(name = "participants")
|
||||||
@CollectionTable(name = "dummy_deal_states_participants", joinColumns = arrayOf(
|
@CollectionTable(name = "dummy_deal_states_participants", joinColumns = arrayOf(
|
||||||
|
Loading…
Reference in New Issue
Block a user